Repository: openyurtio/openyurt
Pull request #661 (2021-12-07 13:07:30)

split the yurthub certificate into server and client certificates
<!--  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?
> Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading whitespace from that line:
> /kind bug
> /kind documentation
> /kind enhancement
> /kind good-first-issue
> /kind feature
> /kind question
> /kind design
> /sig ai
> /sig iot
> /sig network
> /sig storage
> /sig storage

/kind bug


#### What this PR does / why we need it:
Restore the original certificate mode of yurthub, which is, server and client certificates are managed separately. Avoid certificate verification errors when some components that have strict requirements on the use of certificates connect to yurthub. (for example, fluend)

#### 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:
<!--
use this label to assign your reviewer
/assign @your_reviewer
-->
/assign @rambohe-ch please take review

#### 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 
-->


openyurt-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/openyurtio/openyurt/pull/661#" title="Author self-approved">qclc</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **rambohe-ch**
You can assign the PR to them by writing `/assign @rambohe-ch` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=openyurtio%2Fopenyurt).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/openyurtio/openyurt/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["rambohe-ch"]} -->

Patch:
diff --git a/cmd/yurt-controller-manager/app/controllermanager.go b/cmd/yurt-controller-manager/app/controllermanager.go
index 4cce627b..0a4de855 100644
--- a/cmd/yurt-controller-manager/app/controllermanager.go
+++ b/cmd/yurt-controller-manager/app/controllermanager.go
@@ -31,6 +31,7 @@ import (
 	"time"
 
 	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
 	"k8s.io/apimachinery/pkg/util/sets"
 	"k8s.io/apimachinery/pkg/util/uuid"
 	"k8s.io/apimachinery/pkg/util/wait"
@@ -46,13 +47,12 @@ import (
 	"k8s.io/component-base/cli/globalflag"
 	"k8s.io/component-base/version"
 	"k8s.io/klog/v2"
-	genericcontrollermanager "k8s.io/kubernetes/cmd/controller-manager/app"
-	"k8s.io/kubernetes/pkg/controller"
-	utilflag "k8s.io/kubernetes/pkg/util/flag"
 
 	"github.com/openyurtio/openyurt/cmd/yurt-controller-manager/app/config"
 	"github.com/openyurtio/openyurt/cmd/yurt-controller-manager/app/options"
 	yurtctrlmgrconfig "github.com/openyurtio/openyurt/pkg/controller/apis/config"
+	genericcontrollermanager "github.com/openyurtio/openyurt/pkg/controller/kubernetes/cmd/controller-manager/app"
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller"
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
 )
 
@@ -84,7 +84,7 @@ current state towards the desired state. now only nodelifecycle controller is pr
 			}
 			fmt.Printf("%s version: %#v\n", projectinfo.GetYurtControllerManagerName(), projectinfo.Get())
 
-			utilflag.PrintFlags(cmd.Flags())
+			PrintFlags(cmd.Flags())
 
 			c, err := s.Config(KnownControllers(), ControllersDisabledByDefault.List())
 			if err != nil {
@@ -346,3 +346,10 @@ func StartControllers(ctx ControllerContext, controllers map[string]InitFunc, un
 
 	return nil
 }
+
+// PrintFlags logs the flags in the flagset
+func PrintFlags(flags *pflag.FlagSet) {
+	flags.VisitAll(func(flag *pflag.Flag) {
+		klog.V(1).Infof("FLAG: --%s=%q", flag.Name, flag.Value)
+	})
+}
diff --git a/cmd/yurt-controller-manager/app/core.go b/cmd/yurt-controller-manager/app/core.go
index 4986e970..a43a7fbb 100644
--- a/cmd/yurt-controller-manager/app/core.go
+++ b/cmd/yurt-controller-manager/app/core.go
@@ -46,7 +46,7 @@ func startNodeLifecycleController(ctx ControllerContext) (http.Handler, bool, er
 		ctx.ComponentConfig.NodeLifecycleController.SecondaryNodeEvictionRate,
 		ctx.ComponentConfig.NodeLifecycleController.LargeClusterSizeThreshold,
 		ctx.ComponentConfig.NodeLifecycleController.UnhealthyZoneThreshold,
-		ctx.ComponentConfig.NodeLifecycleController.EnableTaintManager,
+		*ctx.ComponentConfig.NodeLifecycleController.EnableTaintManager,
 	)
 	if err != nil {
 		return nil, true, err
diff --git a/cmd/yurt-controller-manager/app/options/generic.go b/cmd/yurt-controller-manager/app/options/generic.go
index fff40f68..1958b149 100644
--- a/cmd/yurt-controller-manager/app/options/generic.go
+++ b/cmd/yurt-controller-manager/app/options/generic.go
@@ -23,8 +23,9 @@ import (
 	"k8s.io/apimachinery/pkg/util/sets"
 	cliflag "k8s.io/component-base/cli/flag"
 	componentbaseconfig "k8s.io/component-base/config"
-	"k8s.io/kubernetes/pkg/client/leaderelectionconfig"
 	kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
+
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/client/leaderelectionconfig"
 )
 
 // GenericControllerManagerConfigurationOptions holds the options which are generic.
diff --git a/cmd/yurt-controller-manager/app/options/nodelifecyclecontroller.go b/cmd/yurt-controller-manager/app/options/nodelifecyclecontroller.go
index c548ab60..9bb15b05 100644
--- a/cmd/yurt-controller-manager/app/options/nodelifecyclecontroller.go
+++ b/cmd/yurt-controller-manager/app/options/nodelifecyclecontroller.go
@@ -18,7 +18,7 @@ package options
 
 import (
 	"github.com/spf13/pflag"
-	nodelifecycleconfig "k8s.io/kubernetes/pkg/controller/nodelifecycle/config"
+	nodelifecycleconfig "k8s.io/kube-controller-manager/config/v1alpha1"
 )
 
 // NodeLifecycleControllerOptions holds the NodeLifecycleController options.
@@ -43,7 +43,7 @@ func (o *NodeLifecycleControllerOptions) AddFlags(fs *pflag.FlagSet) {
 	fs.Float32Var(&o.SecondaryNodeEvictionRate, "secondary-node-eviction-rate", 0.01, "Number of nodes per second on which pods are deleted in case of node failure when a zone is unhealthy (see --unhealthy-zone-threshold for definition of healthy/unhealthy). Zone refers to entire cluster in non-multizone clusters. This value is implicitly overridden to 0 if the cluster size is smaller than --large-cluster-size-threshold.")
 	fs.Int32Var(&o.LargeClusterSizeThreshold, "large-cluster-size-threshold", 50, "Number of nodes from which NodeController treats the cluster as large for the eviction logic purposes. --secondary-node-eviction-rate is implicitly overridden to 0 for clusters this size or smaller.")
 	fs.Float32Var(&o.UnhealthyZoneThreshold, "unhealthy-zone-threshold", 0.55, "Fraction of Nodes in a zone which needs to be not Ready (minimum 3) for zone to be treated as unhealthy. ")
-	fs.BoolVar(&o.EnableTaintManager, "enable-taint-manager", o.EnableTaintManager, "WARNING: Beta feature. If set to true enables NoExecute Taints and will evict all not-tolerating Pod running on Nodes tainted with this kind of Taints.")
+	fs.BoolVar(o.EnableTaintManager, "enable-taint-manager", *o.EnableTaintManager, "WARNING: Beta feature. If set to true enables NoExecute Taints and will evict all not-tolerating Pod running on Nodes tainted with this kind of Taints.")
 }
 
 // ApplyTo fills up NodeLifecycleController config with options.
diff --git a/cmd/yurt-controller-manager/app/options/options.go b/cmd/yurt-controller-manager/app/options/options.go
index 090de7eb..acaee6a1 100644
--- a/cmd/yurt-controller-manager/app/options/options.go
+++ b/cmd/yurt-controller-manager/app/options/options.go
@@ -36,9 +36,9 @@ import (
 	cliflag "k8s.io/component-base/cli/flag"
 	componentbaseconfig "k8s.io/component-base/config"
 	"k8s.io/klog/v2"
+	nodelifecycleconfig "k8s.io/kube-controller-manager/config/v1alpha1"
 	kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
-	nodelifecycleconfig "k8s.io/kubernetes/pkg/controller/nodelifecycle/config"
-	_ "k8s.io/kubernetes/pkg/features"
+	utilpointer "k8s.io/utils/pointer"
 
 	yurtcontrollerconfig "github.com/openyurtio/openyurt/cmd/yurt-controller-manager/app/config"
 )
@@ -83,7 +83,7 @@ func NewYurtControllerManagerOptions() (*YurtControllerManagerOptions, error) {
 		Generic: NewGenericControllerManagerConfigurationOptions(&generic),
 		NodeLifecycleController: &NodeLifecycleControllerOptions{
 			NodeLifecycleControllerConfiguration: &nodelifecycleconfig.NodeLifecycleControllerConfiguration{
-				EnableTaintManager:     true,
+				EnableTaintManager:     utilpointer.BoolPtr(true),
 				PodEvictionTimeout:     metav1.Duration{Duration: 5 * time.Minute},
 				NodeMonitorGracePeriod: metav1.Duration{Duration: 40 * time.Second},
 				NodeStartupGracePeriod: metav1.Duration{Duration: 60 * time.Second},
diff --git a/cmd/yurthub/app/options/options.go b/cmd/yurthub/app/options/options.go
index 189e502c..fff32908 100644
--- a/cmd/yurthub/app/options/options.go
+++ b/cmd/yurthub/app/options/options.go
@@ -138,7 +138,7 @@ func (o *YurtHubOptions) AddFlags(fs *pflag.FlagSet) {
 	fs.StringVar(&o.YurtHubProxySecurePort, "proxy-secure-port", o.YurtHubProxySecurePort, "the port on which to proxy HTTPS requests to kube-apiserver")
 	fs.StringVar(&o.ServerAddr, "server-addr", o.ServerAddr, "the address of Kubernetes kube-apiserver,the format is: \"server1,server2,...\"")
 	fs.StringVar(&o.CertMgrMode, "cert-mgr-mode", o.CertMgrMode, "the cert manager mode, hubself: auto generate client cert for hub agent.")
-	fs.StringVar(&o.YurtHubCertOrganizations, "hub-cert-organizations", o.YurtHubCertOrganizations, "Organizations that will be added into hub's certificate in hubself cert-mgr-mode, the format is: certOrg1,certOrg1,...")
+	fs.StringVar(&o.YurtHubCertOrganizations, "hub-cert-organizations", o.YurtHubCertOrganizations, "Organizations that will be added into hub's client certificate in hubself cert-mgr-mode, the format is: certOrg1,certOrg1,...")
 	fs.StringVar(&o.KubeletRootCAFilePath, "kubelet-ca-file", o.KubeletRootCAFilePath, "the ca file path used by kubelet.")
 	fs.StringVar(&o.KubeletPairFilePath, "kubelet-client-certificate", o.KubeletPairFilePath, "the path of kubelet client certificate file.")
 	fs.IntVar(&o.GCFrequency, "gc-frequency", o.GCFrequency, "the frequency to gc cache in storage(unit: minute).")
diff --git a/cmd/yurthub/app/start.go b/cmd/yurthub/app/start.go
index ddedce8f..05ab84ee 100644
--- a/cmd/yurthub/app/start.go
+++ b/cmd/yurthub/app/start.go
@@ -124,7 +124,7 @@ func Run(cfg *config.YurtHubConfiguration, stopCh <-chan struct{}) error {
 	trace++
 
 	klog.Infof("%d. create tls config for secure servers ", trace)
-	cfg.TLSConfig, err = certificate.GenTLSConfigUseCertMgr(certManager, filepath.Join(cfg.RootDir, "pki"))
+	cfg.TLSConfig, err = server.GenUseCertMgrAndTLSConfig(restConfigMgr, certManager, filepath.Join(cfg.RootDir, "pki"), cfg.YurtHubProxyServerSecureDummyAddr, stopCh)
 	if err != nil {
 		return fmt.Errorf("could not create tls config, %v", err)
 	}
diff --git a/go.mod b/go.mod
index 461bac59..902ce03b 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
 	github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd
 	github.com/docker/docker v17.12.0-ce-rc1.0.20200531234253-77e06fda0c94+incompatible // indirect
 	github.com/emicklei/go-restful v2.12.0+incompatible // indirect
+	github.com/evanphx/json-patch v4.5.0+incompatible
 	github.com/go-openapi/spec v0.19.8 // indirect
 	github.com/google/uuid v1.1.1
 	github.com/gorilla/mux v1.7.4
@@ -21,7 +22,7 @@ require (
 	github.com/prometheus/client_golang v1.7.1
 	github.com/spf13/cobra v1.0.0
 	github.com/spf13/pflag v1.0.5
-	github.com/stretchr/testify v1.4.0
+	github.com/stretchr/testify v1.7.0
 	github.com/vishvananda/netlink v1.0.0
 	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
 	golang.org/x/sys v0.0.0-20200519105757-fe76b779f299
@@ -36,11 +37,12 @@ require (
 	k8s.io/client-go v0.19.2
 	k8s.io/cluster-bootstrap v0.0.0
 	k8s.io/component-base v0.18.8
-	k8s.io/klog/v2 v2.4.0
+	k8s.io/klog/v2 v2.9.0
+	k8s.io/kube-controller-manager v0.0.0
 	k8s.io/kubectl v0.0.0
 	k8s.io/kubelet v0.0.0
 	k8s.io/kubernetes v1.18.8
-	k8s.io/utils v0.0.0-20200603063816-c1c6865ac451
+	k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a
 	sigs.k8s.io/apiserver-network-proxy v0.0.15
 )
 
diff --git a/go.sum b/go.sum
index 1993f21d..e2d0e65b 100644
--- a/go.sum
+++ b/go.sum
@@ -164,8 +164,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
-github.com/go-logr/logr v0.2.0 h1:QvGt2nLcHH0WK9orKa+ppBPAxREcH364nPUedEpK0TY=
-github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
+github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
+github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
 github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
 github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
@@ -576,8 +576,9 @@ github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
 github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
 github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM=
@@ -835,6 +836,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
 gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
@@ -875,9 +878,10 @@ k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
 k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
 k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
-k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ=
-k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
+k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM=
+k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
 k8s.io/kube-aggregator v0.18.8/go.mod h1:CyLoGZB+io8eEwnn+6RbV7QWJQhj8a3TBH8ZM8sLbhI=
+k8s.io/kube-controller-manager v0.18.8 h1:9bFm6s+eyWTKB0cI475xFJZazpnb0ld1CHPT3U/g/fY=
 k8s.io/kube-controller-manager v0.18.8/go.mod h1:IYZteddXJFD1TVgAw8eRP3c9OOA2WtHdXdE8aH6gXnc=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6 h1:Oh3Mzx5pJ+yIumsAD0MOECPVeXsVot0UkiaCGVyfGQY=
 k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
@@ -896,8 +900,9 @@ k8s.io/sample-apiserver v0.18.8/go.mod h1:qXPfVwaZwM2owoSMNRRm9vw+HNJGLNsBpGckv1
 k8s.io/system-validators v1.0.4 h1:sW57tJ/ciqOVbbTLN+ZNy64MJMNqUuiwrirQv8IR2Kw=
 k8s.io/system-validators v1.0.4/go.mod h1:HgSgTg4NAGNoYYjKsUyk52gdNi2PVDswQ9Iyn66R7NI=
 k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
-k8s.io/utils v0.0.0-20200603063816-c1c6865ac451 h1:v8ud2Up6QK1lNOKFgiIVrZdMg7MpmSnvtrOieolJKoE=
 k8s.io/utils v0.0.0-20200603063816-c1c6865ac451/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
+k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g=
+k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
 modernc.org/cc v1.0.0/go.mod h1:1Sk4//wdnYJiUIxnW8ddKpaOJCF37yAdqYnkxUpaYxw=
 modernc.org/golex v1.0.0/go.mod h1:b/QX9oBD/LhixY6NDh+IdGv17hgB+51fET1i2kPSmvk=
 modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
diff --git a/pkg/controller/apis/config/types.go b/pkg/controller/apis/config/types.go
index 7ecbb56b..1713485a 100644
--- a/pkg/controller/apis/config/types.go
+++ b/pkg/controller/apis/config/types.go
@@ -18,8 +18,8 @@ package config
 
 import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	nodelifecycleconfig "k8s.io/kube-controller-manager/config/v1alpha1"
 	kubectrlmgrconfig "k8s.io/kubernetes/pkg/controller/apis/config"
-	nodelifecycleconfig "k8s.io/kubernetes/pkg/controller/nodelifecycle/config"
 )
 
 // YurtControllerManagerConfiguration contains elements describing yurt-controller manager.
diff --git a/pkg/controller/kubernetes/client/leaderelectionconfig/config.go b/pkg/controller/kubernetes/client/leaderelectionconfig/config.go
new file mode 100644
index 00000000..bbdc977c
--- /dev/null
+++ b/pkg/controller/kubernetes/client/leaderelectionconfig/config.go
@@ -0,0 +1,52 @@
+/*
+Copyright 2017 The Kubernetes 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 leaderelectionconfig
+
+import (
+	"github.com/spf13/pflag"
+	componentbaseconfig "k8s.io/component-base/config"
+)
+
+// BindFlags binds the LeaderElectionConfiguration struct fields to a flagset
+func BindFlags(l *componentbaseconfig.LeaderElectionConfiguration, fs *pflag.FlagSet) {
+	fs.BoolVar(&l.LeaderElect, "leader-elect", l.LeaderElect, ""+
+		"Start a leader election client and gain leadership before "+
+		"executing the main loop. Enable this when running replicated "+
+		"components for high availability.")
+	fs.DurationVar(&l.LeaseDuration.Duration, "leader-elect-lease-duration", l.LeaseDuration.Duration, ""+
+		"The duration that non-leader candidates will wait after observing a leadership "+
+		"renewal until attempting to acquire leadership of a led but unrenewed leader "+
+		"slot. This is effectively the maximum duration that a leader can be stopped "+
+		"before it is replaced by another candidate. This is only applicable if leader "+
+		"election is enabled.")
+	fs.DurationVar(&l.RenewDeadline.Duration, "leader-elect-renew-deadline", l.RenewDeadline.Duration, ""+
+		"The interval between attempts by the acting master to renew a leadership slot "+
+		"before it stops leading. This must be less than or equal to the lease duration. "+
+		"This is only applicable if leader election is enabled.")
+	fs.DurationVar(&l.RetryPeriod.Duration, "leader-elect-retry-period", l.RetryPeriod.Duration, ""+
+		"The duration the clients should wait between attempting acquisition and renewal "+
+		"of a leadership. This is only applicable if leader election is enabled.")
+	fs.StringVar(&l.ResourceLock, "leader-elect-resource-lock", l.ResourceLock, ""+
+		"The type of resource object that is used for locking during "+
+		"leader election. Supported options are `endpoints` (default) and `configmaps`.")
+	fs.StringVar(&l.ResourceName, "leader-elect-resource-name", l.ResourceName, ""+
+		"The name of resource object that is used for locking during "+
+		"leader election.")
+	fs.StringVar(&l.ResourceNamespace, "leader-elect-resource-namespace", l.ResourceNamespace, ""+
+		"The namespace of resource object that is used for locking during "+
+		"leader election.")
+}
diff --git a/pkg/controller/kubernetes/cmd/controller-manager/app/helper.go b/pkg/controller/kubernetes/cmd/controller-manager/app/helper.go
new file mode 100644
index 00000000..1807a38c
--- /dev/null
+++ b/pkg/controller/kubernetes/cmd/controller-manager/app/helper.go
@@ -0,0 +1,80 @@
+/*
+Copyright 2018 The Kubernetes 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 app
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"time"
+
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/apimachinery/pkg/util/wait"
+	clientset "k8s.io/client-go/kubernetes"
+	"k8s.io/klog/v2"
+)
+
+// WaitForAPIServer waits for the API Server's /healthz endpoint to report "ok" with timeout.
+func WaitForAPIServer(client clientset.Interface, timeout time.Duration) error {
+	var lastErr error
+
+	err := wait.PollImmediate(time.Second, timeout, func() (bool, error) {
+		healthStatus := 0
+		result := client.Discovery().RESTClient().Get().AbsPath("/healthz").Do(context.TODO()).StatusCode(&healthStatus)
+		if result.Error() != nil {
+			lastErr = fmt.Errorf("failed to get apiserver /healthz status: %v", result.Error())
+			return false, nil
+		}
+		if healthStatus != http.StatusOK {
+			content, _ := result.Raw()
+			lastErr = fmt.Errorf("APIServer isn't healthy: %v", string(content))
+			klog.Warningf("APIServer isn't healthy yet: %v. Waiting a little while.", string(content))
+			return false, nil
+		}
+
+		return true, nil
+	})
+
+	if err != nil {
+		return fmt.Errorf("%v: %v", err, lastErr)
+	}
+
+	return nil
+}
+
+// IsControllerEnabled check if a specified controller enabled or not.
+func IsControllerEnabled(name string, disabledByDefaultControllers sets.String, controllers []string) bool {
+	hasStar := false
+	for _, ctrl := range controllers {
+		if ctrl == name {
+			return true
+		}
+		if ctrl == "-"+name {
+			return false
+		}
+		if ctrl == "*" {
+			hasStar = true
+		}
+	}
+	// if we get here, there was no explicit choice
+	if !hasStar {
+		// nothing on by default
+		return false
+	}
+
+	return !disabledByDefaultControllers.Has(name)
+}
diff --git a/pkg/controller/kubernetes/cmd/controller-manager/app/serve.go b/pkg/controller/kubernetes/cmd/controller-manager/app/serve.go
new file mode 100644
index 00000000..85e052fe
--- /dev/null
+++ b/pkg/controller/kubernetes/cmd/controller-manager/app/serve.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2018 The Kubernetes Authors.
+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 app
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	goruntime "runtime"
+	"sync"
+
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
+	apirequest "k8s.io/apiserver/pkg/endpoints/request"
+	apiserver "k8s.io/apiserver/pkg/server"
+	genericfilters "k8s.io/apiserver/pkg/server/filters"
+	"k8s.io/apiserver/pkg/server/healthz"
+	"k8s.io/apiserver/pkg/server/mux"
+	"k8s.io/apiserver/pkg/server/routes"
+	componentbaseconfig "k8s.io/component-base/config"
+	"k8s.io/component-base/metrics/legacyregistry"
+	_ "k8s.io/component-base/metrics/prometheus/workqueue" // for workqueue metric registration
+)
+
+var (
+	Scheme = runtime.NewScheme()
+	Codecs = serializer.NewCodecFactory(Scheme)
+)
+
+// BuildHandlerChain builds a handler chain with a base handler and CompletedConfig.
+func BuildHandlerChain(apiHandler http.Handler, authorizationInfo *apiserver.AuthorizationInfo, authenticationInfo *apiserver.AuthenticationInfo) http.Handler {
+	requestInfoResolver := &apirequest.RequestInfoFactory{}
+	failedHandler := genericapifilters.Unauthorized(Codecs, false)
+
+	handler := apiHandler
+	if authorizationInfo != nil {
+		handler = genericapifilters.WithAuthorization(apiHandler, authorizationInfo.Authorizer, Codecs)
+	}
+	if authenticationInfo != nil {
+		handler = genericapifilters.WithAuthentication(handler, authenticationInfo.Authenticator, failedHandler, nil)
+	}
+	handler = genericapifilters.WithRequestInfo(handler, requestInfoResolver)
+	handler = genericapifilters.WithCacheControl(handler)
+	handler = genericfilters.WithPanicRecovery(handler)
+
+	return handler
+}
+
+// NewBaseHandler takes in CompletedConfig and returns a handler.
+func NewBaseHandler(c *componentbaseconfig.DebuggingConfiguration, checks ...healthz.HealthChecker) *mux.PathRecorderMux {
+	mux := mux.NewPathRecorderMux("controller-manager")
+	healthz.InstallHandler(mux, checks...)
+	if c.EnableProfiling {
+		routes.Profiling{}.Install(mux)
+		if c.EnableContentionProfiling {
+			goruntime.SetBlockProfileRate(1)
+		}
+	}
+	InstallHandler(mux)
+	//lint:ignore SA1019 See the Metrics Stability Migration KEP
+	mux.Handle("/metrics", legacyregistry.Handler())
+
+	return mux
+}
+
+// The following comes from k8s.io/kubernetes/pkg/util/configz"
+var (
+	configsGuard sync.RWMutex
+	configs      = map[string]*Config{}
+)
+
+// Config is a handle to a ComponentConfig object. Don't create these directly;
+// use New() instead.
+type Config struct {
+	val interface{}
+}
+
+// InstallHandler adds an HTTP handler on the given mux for the "/configz"
+// endpoint which serves all registered ComponentConfigs in JSON format.
+func InstallHandler(m configzmux) {
+	m.Handle("/configz", http.HandlerFunc(handle))
+}
+
+type configzmux interface {
+	Handle(string, http.Handler)
+}
+
+func handle(w http.ResponseWriter, r *http.Request) {
+	if err := write(w); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func write(w http.ResponseWriter) error {
+	var b []byte
+	var err error
+	func() {
+		configsGuard.RLock()
+		defer configsGuard.RUnlock()
+		b, err = json.Marshal(configs)
+	}()
+	if err != nil {
+		return fmt.Errorf("error marshaling json: %v", err)
+	}
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("X-Content-Type-Options", "nosniff")
+	_, err = w.Write(b)
+	return err
+}
diff --git a/pkg/controller/kubernetes/controller/client_builder.go b/pkg/controller/kubernetes/controller/client_builder.go
new file mode 100644
index 00000000..734b586d
--- /dev/null
+++ b/pkg/controller/kubernetes/controller/client_builder.go
@@ -0,0 +1,266 @@
+/*
+Copyright 2016 The Kubernetes 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 controller
+
+import (
+	"context"
+	"fmt"
+	"time"
+
+	v1authenticationapi "k8s.io/api/authentication/v1"
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/fields"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/apimachinery/pkg/watch"
+	apiserverserviceaccount "k8s.io/apiserver/pkg/authentication/serviceaccount"
+	clientset "k8s.io/client-go/kubernetes"
+	v1authentication "k8s.io/client-go/kubernetes/typed/authentication/v1"
+	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
+	restclient "k8s.io/client-go/rest"
+	"k8s.io/client-go/tools/cache"
+	watchtools "k8s.io/client-go/tools/watch"
+	"k8s.io/klog/v2"
+)
+
+const SecretTypeField = "type"
+
+var (
+	Scheme = runtime.NewScheme()
+	Codecs = serializer.NewCodecFactory(Scheme)
+)
+
+// ControllerClientBuilder allows you to get clients and configs for controllers
+// Please note a copy also exists in staging/src/k8s.io/cloud-provider/cloud.go
+// TODO: Extract this into a separate controller utilities repo (issues/68947)
+type ControllerClientBuilder interface {
+	Config(name string) (*restclient.Config, error)
+	ConfigOrDie(name string) *restclient.Config
+	Client(name string) (clientset.Interface, error)
+	ClientOrDie(name string) clientset.Interface
+}
+
+// SimpleControllerClientBuilder returns a fixed client with different user agents
+type SimpleControllerClientBuilder struct {
+	// ClientConfig is a skeleton config to clone and use as the basis for each controller client
+	ClientConfig *restclient.Config
+}
+
+func (b SimpleControllerClientBuilder) Config(name string) (*restclient.Config, error) {
+	clientConfig := *b.ClientConfig
+	return restclient.AddUserAgent(&clientConfig, name), nil
+}
+
+func (b SimpleControllerClientBuilder) ConfigOrDie(name string) *restclient.Config {
+	clientConfig, err := b.Config(name)
+	if err != nil {
+		klog.Fatal(err)
+	}
+	return clientConfig
+}
+
+func (b SimpleControllerClientBuilder) Client(name string) (clientset.Interface, error) {
+	clientConfig, err := b.Config(name)
+	if err != nil {
+		return nil, err
+	}
+	return clientset.NewForConfig(clientConfig)
+}
+
+func (b SimpleControllerClientBuilder) ClientOrDie(name string) clientset.Interface {
+	client, err := b.Client(name)
+	if err != nil {
+		klog.Fatal(err)
+	}
+	return client
+}
+
+// SAControllerClientBuilder is a ControllerClientBuilder that returns clients identifying as
+// service accounts
+type SAControllerClientBuilder struct {
+	// ClientConfig is a skeleton config to clone and use as the basis for each controller client
+	ClientConfig *restclient.Config
+
+	// CoreClient is used to provision service accounts if needed and watch for their associated tokens
+	// to construct a controller client
+	CoreClient v1core.CoreV1Interface
+
+	// AuthenticationClient is used to check API tokens to make sure they are valid before
+	// building a controller client from them
+	AuthenticationClient v1authentication.AuthenticationV1Interface
+
+	// Namespace is the namespace used to host the service accounts that will back the
+	// controllers.  It must be highly privileged namespace which normal users cannot inspect.
+	Namespace string
+}
+
+// config returns a complete clientConfig for constructing clients.  This is separate in anticipation of composition
+// which means that not all clientsets are known here
+func (b SAControllerClientBuilder) Config(name string) (*restclient.Config, error) {
+	sa, err := getOrCreateServiceAccount(b.CoreClient, b.Namespace, name)
+	if err != nil {
+		return nil, err
+	}
+
+	var clientConfig *restclient.Config
+	fieldSelector := fields.SelectorFromSet(map[string]string{
+		SecretTypeField: string(v1.SecretTypeServiceAccountToken),
+	}).String()
+	lw := &cache.ListWatch{
+		ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
+			options.FieldSelector = fieldSelector
+			return b.CoreClient.Secrets(b.Namespace).List(context.TODO(), options)
+		},
+		WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
+			options.FieldSelector = fieldSelector
+			return b.CoreClient.Secrets(b.Namespace).Watch(context.TODO(), options)
+		},
+	}
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+	_, err = watchtools.UntilWithSync(ctx, lw, &v1.Secret{}, nil,
+		func(event watch.Event) (bool, error) {
+			switch event.Type {
+			case watch.Deleted:
+				return false, nil
+			case watch.Error:
+				return false, fmt.Errorf("error watching")
+
+			case watch.Added, watch.Modified:
+				secret, ok := event.Object.(*v1.Secret)
+				if !ok {
+					return false, fmt.Errorf("unexpected object type: %T", event.Object)
+				}
+				if !IsServiceAccountToken(secret, sa) {
+					return false, nil
+				}
+				if len(secret.Data[v1.ServiceAccountTokenKey]) == 0 {
+					return false, nil
+				}
+				validConfig, valid, err := b.getAuthenticatedConfig(sa, string(secret.Data[v1.ServiceAccountTokenKey]))
+				if err != nil {
+					klog.Warningf("error validating API token for %s/%s in secret %s: %v", sa.Namespace, sa.Name, secret.Name, err)
+					// continue watching for good tokens
+					return false, nil
+				}
+				if !valid {
+					klog.Warningf("secret %s contained an invalid API token for %s/%s", secret.Name, sa.Namespace, sa.Name)
+					// try to delete the secret containing the invalid token
+					if err := b.CoreClient.Secrets(secret.Namespace).Delete(context.TODO(), secret.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
+						klog.Warningf("error deleting secret %s containing invalid API token for %s/%s: %v", secret.Name, sa.Namespace, sa.Name, err)
+					}
+					// continue watching for good tokens
+					return false, nil
+				}
+				clientConfig = validConfig
+				return true, nil
+
+			default:
+				return false, fmt.Errorf("unexpected event type: %v", event.Type)
+			}
+		})
+	if err != nil {
+		return nil, fmt.Errorf("unable to get token for service account: %v", err)
+	}
+
+	return clientConfig, nil
+}
+
+func (b SAControllerClientBuilder) getAuthenticatedConfig(sa *v1.ServiceAccount, token string) (*restclient.Config, bool, error) {
+	username := apiserverserviceaccount.MakeUsername(sa.Namespace, sa.Name)
+
+	clientConfig := restclient.AnonymousClientConfig(b.ClientConfig)
+	clientConfig.BearerToken = token
+	restclient.AddUserAgent(clientConfig, username)
+
+	// Try token review first
+	tokenReview := &v1authenticationapi.TokenReview{Spec: v1authenticationapi.TokenReviewSpec{Token: token}}
+	if tokenResult, err := b.AuthenticationClient.TokenReviews().Create(context.TODO(), tokenReview, metav1.CreateOptions{}); err == nil {
+		if !tokenResult.Status.Authenticated {
+			klog.Warningf("Token for %s/%s did not authenticate correctly", sa.Namespace, sa.Name)
+			return nil, false, nil
+		}
+		if tokenResult.Status.User.Username != username {
+			klog.Warningf("Token for %s/%s authenticated as unexpected username: %s", sa.Namespace, sa.Name, tokenResult.Status.User.Username)
+			return nil, false, nil
+		}
+		klog.V(4).Infof("Verified credential for %s/%s", sa.Namespace, sa.Name)
+		return clientConfig, true, nil
+	}
+
+	// If we couldn't run the token review, the API might be disabled or we might not have permission.
+	// Try to make a request to /apis with the token. If we get a 401 we should consider the token invalid.
+	clientConfigCopy := *clientConfig
+	clientConfigCopy.NegotiatedSerializer = Codecs
+	client, err := restclient.UnversionedRESTClientFor(&clientConfigCopy)
+	if err != nil {
+		return nil, false, err
+	}
+	err = client.Get().AbsPath("/apis").Do(context.TODO()).Error()
+	if apierrors.IsUnauthorized(err) {
+		klog.Warningf("Token for %s/%s did not authenticate correctly: %v", sa.Namespace, sa.Name, err)
+		return nil, false, nil
+	}
+
+	return clientConfig, true, nil
+}
+
+func (b SAControllerClientBuilder) ConfigOrDie(name string) *restclient.Config {
+	clientConfig, err := b.Config(name)
+	if err != nil {
+		klog.Fatal(err)
+	}
+	return clientConfig
+}
+
+func (b SAControllerClientBuilder) Client(name string) (clientset.Interface, error) {
+	clientConfig, err := b.Config(name)
+	if err != nil {
+		return nil, err
+	}
+	return clientset.NewForConfig(clientConfig)
+}
+
+func (b SAControllerClientBuilder) ClientOrDie(name string) clientset.Interface {
+	client, err := b.Client(name)
+	if err != nil {
+		klog.Fatal(err)
+	}
+	return client
+}
+
+// IsServiceAccountToken returns true if the secret is a valid api token for the service account
+func IsServiceAccountToken(secret *v1.Secret, sa *v1.ServiceAccount) bool {
+	if secret.Type != v1.SecretTypeServiceAccountToken {
+		return false
+	}
+
+	name := secret.Annotations[v1.ServiceAccountNameKey]
+	uid := secret.Annotations[v1.ServiceAccountUIDKey]
+	if name != sa.Name {
+		// Name must match
+		return false
+	}
+	if len(uid) > 0 && uid != string(sa.UID) {
+		// If UID is specified, it must match
+		return false
+	}
+
+	return true
+}
diff --git a/pkg/controller/kubernetes/controller/controller_utils.go b/pkg/controller/kubernetes/controller/controller_utils.go
new file mode 100644
index 00000000..9e3a29f3
--- /dev/null
+++ b/pkg/controller/kubernetes/controller/controller_utils.go
@@ -0,0 +1,248 @@
+/*
+Copyright 2014 The Kubernetes Authors.
+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 controller
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"time"
+
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/strategicpatch"
+	"k8s.io/apimachinery/pkg/util/wait"
+	clientset "k8s.io/client-go/kubernetes"
+	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
+	clientretry "k8s.io/client-go/util/retry"
+	"k8s.io/klog/v2"
+
+	taintutils "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/taints"
+)
+
+var UpdateTaintBackoff = wait.Backoff{
+	Steps:    5,
+	Duration: 100 * time.Millisecond,
+	Jitter:   1.0,
+}
+
+var UpdateLabelBackoff = wait.Backoff{
+	Steps:    5,
+	Duration: 100 * time.Millisecond,
+	Jitter:   1.0,
+}
+
+// Returns 0 for resyncPeriod in case resyncing is not needed.
+func NoResyncPeriodFunc() time.Duration {
+	return 0
+}
+
+// AddOrUpdateTaintOnNode add taints to the node. If taint was added into node, it'll issue API calls
+// to update nodes; otherwise, no API calls. Return error if any.
+func AddOrUpdateTaintOnNode(c clientset.Interface, nodeName string, taints ...*v1.Taint) error {
+	if len(taints) == 0 {
+		return nil
+	}
+	firstTry := true
+	return clientretry.RetryOnConflict(UpdateTaintBackoff, func() error {
+		var err error
+		var oldNode *v1.Node
+		// First we try getting node from the API server cache, as it's cheaper. If it fails
+		// we get it from etcd to be sure to have fresh data.
+		if firstTry {
+			oldNode, err = c.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{ResourceVersion: "0"})
+			firstTry = false
+		} else {
+			oldNode, err = c.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
+		}
+		if err != nil {
+			return err
+		}
+
+		var newNode *v1.Node
+		oldNodeCopy := oldNode
+		updated := false
+		for _, taint := range taints {
+			curNewNode, ok, err := taintutils.AddOrUpdateTaint(oldNodeCopy, taint)
+			if err != nil {
+				return fmt.Errorf("failed to update taint of node")
+			}
+			updated = updated || ok
+			newNode = curNewNode
+			oldNodeCopy = curNewNode
+		}
+		if !updated {
+			return nil
+		}
+		return PatchNodeTaints(c, nodeName, oldNode, newNode)
+	})
+}
+
+// RemoveTaintOffNode is for cleaning up taints temporarily added to node,
+// won't fail if target taint doesn't exist or has been removed.
+// If passed a node it'll check if there's anything to be done, if taint is not present it won't issue
+// any API calls.
+func RemoveTaintOffNode(c clientset.Interface, nodeName string, node *v1.Node, taints ...*v1.Taint) error {
+	if len(taints) == 0 {
+		return nil
+	}
+	// Short circuit for limiting amount of API calls.
+	if node != nil {
+		match := false
+		for _, taint := range taints {
+			if taintutils.TaintExists(node.Spec.Taints, taint) {
+				match = true
+				break
+			}
+		}
+		if !match {
+			return nil
+		}
+	}
+
+	firstTry := true
+	return clientretry.RetryOnConflict(UpdateTaintBackoff, func() error {
+		var err error
+		var oldNode *v1.Node
+		// First we try getting node from the API server cache, as it's cheaper. If it fails
+		// we get it from etcd to be sure to have fresh data.
+		if firstTry {
+			oldNode, err = c.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{ResourceVersion: "0"})
+			firstTry = false
+		} else {
+			oldNode, err = c.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
+		}
+		if err != nil {
+			return err
+		}
+
+		var newNode *v1.Node
+		oldNodeCopy := oldNode
+		updated := false
+		for _, taint := range taints {
+			curNewNode, ok, err := taintutils.RemoveTaint(oldNodeCopy, taint)
+			if err != nil {
+				return fmt.Errorf("failed to remove taint of node")
+			}
+			updated = updated || ok
+			newNode = curNewNode
+			oldNodeCopy = curNewNode
+		}
+		if !updated {
+			return nil
+		}
+		return PatchNodeTaints(c, nodeName, oldNode, newNode)
+	})
+}
+
+// PatchNodeTaints patches node's taints.
+func PatchNodeTaints(c clientset.Interface, nodeName string, oldNode *v1.Node, newNode *v1.Node) error {
+	oldData, err := json.Marshal(oldNode)
+	if err != nil {
+		return fmt.Errorf("failed to marshal old node %#v for node %q: %v", oldNode, nodeName, err)
+	}
+
+	newTaints := newNode.Spec.Taints
+	newNodeClone := oldNode.DeepCopy()
+	newNodeClone.Spec.Taints = newTaints
+	newData, err := json.Marshal(newNodeClone)
+	if err != nil {
+		return fmt.Errorf("failed to marshal new node %#v for node %q: %v", newNodeClone, nodeName, err)
+	}
+
+	patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
+	if err != nil {
+		return fmt.Errorf("failed to create patch for node %q: %v", nodeName, err)
+	}
+
+	_, err = c.CoreV1().Nodes().Patch(context.TODO(), nodeName, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{})
+	return err
+}
+
+func AddOrUpdateLabelsOnNode(kubeClient clientset.Interface, nodeName string, labelsToUpdate map[string]string) error {
+	firstTry := true
+	return clientretry.RetryOnConflict(UpdateLabelBackoff, func() error {
+		var err error
+		var node *v1.Node
+		// First we try getting node from the API server cache, as it's cheaper. If it fails
+		// we get it from etcd to be sure to have fresh data.
+		if firstTry {
+			node, err = kubeClient.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{ResourceVersion: "0"})
+			firstTry = false
+		} else {
+			node, err = kubeClient.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
+		}
+		if err != nil {
+			return err
+		}
+
+		// Make a copy of the node and update the labels.
+		newNode := node.DeepCopy()
+		if newNode.Labels == nil {
+			newNode.Labels = make(map[string]string)
+		}
+		for key, value := range labelsToUpdate {
+			newNode.Labels[key] = value
+		}
+
+		oldData, err := json.Marshal(node)
+		if err != nil {
+			return fmt.Errorf("failed to marshal the existing node %#v: %v", node, err)
+		}
+		newData, err := json.Marshal(newNode)
+		if err != nil {
+			return fmt.Errorf("failed to marshal the new node %#v: %v", newNode, err)
+		}
+		patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, &v1.Node{})
+		if err != nil {
+			return fmt.Errorf("failed to create a two-way merge patch: %v", err)
+		}
+		if _, err := kubeClient.CoreV1().Nodes().Patch(context.TODO(), node.Name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}); err != nil {
+			return fmt.Errorf("failed to patch the node: %v", err)
+		}
+		return nil
+	})
+}
+
+func getOrCreateServiceAccount(coreClient v1core.CoreV1Interface, namespace, name string) (*v1.ServiceAccount, error) {
+	sa, err := coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	if err == nil {
+		return sa, nil
+	}
+	if !apierrors.IsNotFound(err) {
+		return nil, err
+	}
+
+	// Create the namespace if we can't verify it exists.
+	// Tolerate errors, since we don't know whether this component has namespace creation permissions.
+	if _, err := coreClient.Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}); apierrors.IsNotFound(err) {
+		if _, err = coreClient.Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}, metav1.CreateOptions{}); err != nil && !apierrors.IsAlreadyExists(err) {
+			klog.Warningf("create non-exist namespace %s failed:%v", namespace, err)
+		}
+	}
+
+	// Create the service account
+	sa, err = coreClient.ServiceAccounts(namespace).Create(context.TODO(), &v1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}}, metav1.CreateOptions{})
+	if apierrors.IsAlreadyExists(err) {
+		// If we're racing to init and someone else already created it, re-fetch
+		return coreClient.ServiceAccounts(namespace).Get(context.TODO(), name, metav1.GetOptions{})
+	}
+	return sa, err
+}
diff --git a/pkg/controller/kubernetes/controller/util/node/controller_utils.go b/pkg/controller/kubernetes/controller/util/node/controller_utils.go
new file mode 100644
index 00000000..04f49e9c
--- /dev/null
+++ b/pkg/controller/kubernetes/controller/util/node/controller_utils.go
@@ -0,0 +1,152 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+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 node
+
+import (
+	"context"
+	"fmt"
+
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	utilerrors "k8s.io/apimachinery/pkg/util/errors"
+	clientset "k8s.io/client-go/kubernetes"
+	appsv1listers "k8s.io/client-go/listers/apps/v1"
+	"k8s.io/client-go/tools/record"
+	"k8s.io/klog/v2"
+
+	nodepkg "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/node"
+)
+
+// DeletePods will delete all pods from master running on given node,
+// and return true if any pods were deleted, or were found pending
+// deletion.
+func DeletePods(kubeClient clientset.Interface, pods []*v1.Pod, recorder record.EventRecorder, nodeName, nodeUID string, daemonStore appsv1listers.DaemonSetLister) (bool, error) {
+	remaining := false
+	var updateErrList []error
+
+	if len(pods) > 0 {
+		RecordNodeEvent(recorder, nodeName, nodeUID, v1.EventTypeNormal, "DeletingAllPods", fmt.Sprintf("Deleting all Pods from Node %v.", nodeName))
+	}
+
+	for i := range pods {
+		// Defensive check, also needed for tests.
+		if pods[i].Spec.NodeName != nodeName {
+			continue
+		}
+
+		// Pod will be modified, so making copy is required.
+		pod := pods[i].DeepCopy()
+		// Set reason and message in the pod object.
+		if _, err := SetPodTerminationReason(kubeClient, pod, nodeName); err != nil {
+			if apierrors.IsConflict(err) {
+				updateErrList = append(updateErrList,
+					fmt.Errorf("update status failed for pod %q: %v", Pod(pod), err))
+				continue
+			}
+		}
+		// if the pod has already been marked for deletion, we still return true that there are remaining pods.
+		if pod.DeletionGracePeriodSeconds != nil {
+			remaining = true
+			continue
+		}
+		// if the pod is managed by a daemonset, ignore it
+		if _, err := daemonStore.GetPodDaemonSets(pod); err == nil {
+			// No error means at least one daemonset was found
+			continue
+		}
+
+		klog.V(2).Infof("Starting deletion of pod %v/%v", pod.Namespace, pod.Name)
+		recorder.Eventf(pod, v1.EventTypeNormal, "NodeControllerEviction", "Marking for deletion Pod %s from Node %s", pod.Name, nodeName)
+		if err := kubeClient.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{}); err != nil {
+			if apierrors.IsNotFound(err) {
+				// NotFound error means that pod was already deleted.
+				// There is nothing left to do with this pod.
+				continue
+			}
+			return false, err
+		}
+		remaining = true
+	}
+
+	if len(updateErrList) > 0 {
+		return false, utilerrors.NewAggregate(updateErrList)
+	}
+	return remaining, nil
+}
+
+// SetPodTerminationReason attempts to set a reason and message in the
+// pod status, updates it in the apiserver, and returns an error if it
+// encounters one.
+func SetPodTerminationReason(kubeClient clientset.Interface, pod *v1.Pod, nodeName string) (*v1.Pod, error) {
+	if pod.Status.Reason == nodepkg.NodeUnreachablePodReason {
+		return pod, nil
+	}
+
+	pod.Status.Reason = nodepkg.NodeUnreachablePodReason
+	pod.Status.Message = fmt.Sprintf(nodepkg.NodeUnreachablePodMessage, nodeName, pod.Name)
+
+	var updatedPod *v1.Pod
+	var err error
+	if updatedPod, err = kubeClient.CoreV1().Pods(pod.Namespace).UpdateStatus(context.TODO(), pod, metav1.UpdateOptions{}); err != nil {
+		return nil, err
+	}
+	return updatedPod, nil
+}
+
+// RecordNodeEvent records a event related to a node.
+func RecordNodeEvent(recorder record.EventRecorder, nodeName, nodeUID, eventtype, reason, event string) {
+	ref := &v1.ObjectReference{
+		APIVersion: "v1",
+		Kind:       "Node",
+		Name:       nodeName,
+		UID:        types.UID(nodeUID),
+		Namespace:  "",
+	}
+	klog.V(2).Infof("Recording %s event message for node %s", event, nodeName)
+	recorder.Eventf(ref, eventtype, reason, "Node %s event: %s", nodeName, event)
+}
+
+// GetNodeCondition extracts the provided condition from the given status and returns that.
+// Returns nil and -1 if the condition is not present, and the index of the located condition.
+func GetNodeCondition(status *v1.NodeStatus, conditionType v1.NodeConditionType) (int, *v1.NodeCondition) {
+	if status == nil {
+		return -1, nil
+	}
+	for i := range status.Conditions {
+		if status.Conditions[i].Type == conditionType {
+			return i, &status.Conditions[i]
+		}
+	}
+	return -1, nil
+}
+
+// Pod returns a string representing a pod in a consistent human readable format,
+// with pod UID as part of the string.
+func Pod(pod *v1.Pod) string {
+	return PodDesc(pod.Name, pod.Namespace, pod.UID)
+}
+
+// PodDesc returns a string representing a pod in a consistent human readable format,
+// with pod UID as part of the string.
+func PodDesc(podName, podNamespace string, podUID types.UID) string {
+	// Use underscore as the delimiter because it is not allowed in pod name
+	// (DNS subdomain format), while allowed in the container name format.
+	return fmt.Sprintf("%s_%s(%s)", podName, podNamespace, podUID)
+}
diff --git a/pkg/controller/kubernetes/features/kube_features.go b/pkg/controller/kubernetes/features/kube_features.go
new file mode 100644
index 00000000..da328601
--- /dev/null
+++ b/pkg/controller/kubernetes/features/kube_features.go
@@ -0,0 +1,697 @@
+/*
+Copyright 2017 The Kubernetes 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 features
+
+import (
+	"k8s.io/apimachinery/pkg/util/runtime"
+	genericfeatures "k8s.io/apiserver/pkg/features"
+	utilfeature "k8s.io/apiserver/pkg/util/feature"
+	"k8s.io/component-base/featuregate"
+)
+
+const (
+	// Every feature gate should add method here following this template:
+	//
+	// // owner: @username
+	// // alpha: v1.X
+	// MyFeature featuregate.Feature = "MyFeature"
+
+	// owner: @tallclair
+	// beta: v1.4
+	AppArmor featuregate.Feature = "AppArmor"
+
+	// owner: @mtaufen
+	// alpha: v1.4
+	// beta: v1.11
+	DynamicKubeletConfig featuregate.Feature = "DynamicKubeletConfig"
+
+	// owner: @pweil-
+	// alpha: v1.5
+	//
+	// Default userns=host for containers that are using other host namespaces, host mounts, the pod
+	// contains a privileged container, or specific non-namespaced capabilities (MKNOD, SYS_MODULE,
+	// SYS_TIME). This should only be enabled if user namespace remapping is enabled in the docker daemon.
+	ExperimentalHostUserNamespaceDefaultingGate featuregate.Feature = "ExperimentalHostUserNamespaceDefaulting"
+
+	// owner: @jiayingz
+	// beta: v1.10
+	//
+	// Enables support for Device Plugins
+	DevicePlugins featuregate.Feature = "DevicePlugins"
+
+	// owner: @dxist
+	// alpha: v1.16
+	//
+	// Enables support of HPA scaling to zero pods when an object or custom metric is configured.
+	HPAScaleToZero featuregate.Feature = "HPAScaleToZero"
+
+	// owner: @Huang-Wei
+	// beta: v1.13
+	// ga: v1.18
+	//
+	// Changes the logic behind evicting Pods from not ready Nodes
+	// to take advantage of NoExecute Taints and Tolerations.
+	TaintBasedEvictions featuregate.Feature = "TaintBasedEvictions"
+
+	// owner: @mikedanese
+	// alpha: v1.7
+	// beta: v1.12
+	//
+	// Gets a server certificate for the kubelet from the Certificate Signing
+	// Request API instead of generating one self signed and auto rotates the
+	// certificate as expiration approaches.
+	RotateKubeletServerCertificate featuregate.Feature = "RotateKubeletServerCertificate"
+
+	// owner: @mikedanese
+	// beta: v1.8
+	//
+	// Automatically renews the client certificate used for communicating with
+	// the API server as the certificate approaches expiration.
+	RotateKubeletClientCertificate featuregate.Feature = "RotateKubeletClientCertificate"
+
+	// owner: @jinxu
+	// beta: v1.10
+	//
+	// New local storage types to support local storage capacity isolation
+	LocalStorageCapacityIsolation featuregate.Feature = "LocalStorageCapacityIsolation"
+
+	// owner: @gnufied
+	// beta: v1.11
+	// Ability to Expand persistent volumes
+	ExpandPersistentVolumes featuregate.Feature = "ExpandPersistentVolumes"
+
+	// owner: @mlmhl
+	// beta: v1.15
+	// Ability to expand persistent volumes' file system without unmounting volumes.
+	ExpandInUsePersistentVolumes featuregate.Feature = "ExpandInUsePersistentVolumes"
+
+	// owner: @gnufied
+	// alpha: v1.14
+	// beta: v1.16
+	// Ability to expand CSI volumes
+	ExpandCSIVolumes featuregate.Feature = "ExpandCSIVolumes"
+
+	// owner: @verb
+	// alpha: v1.16
+	//
+	// Allows running an ephemeral container in pod namespaces to troubleshoot a running pod.
+	EphemeralContainers featuregate.Feature = "EphemeralContainers"
+
+	// owner: @verb
+	// alpha: v1.10
+	// beta: v1.12
+	// GA: v1.17
+	//
+	// Allows all containers in a pod to share a process namespace.
+	PodShareProcessNamespace featuregate.Feature = "PodShareProcessNamespace"
+
+	// owner: @sjenning
+	// alpha: v1.11
+	//
+	// Allows resource reservations at the QoS level preventing pods at lower QoS levels from
+	// bursting into resources requested at higher QoS levels (memory only for now)
+	QOSReserved featuregate.Feature = "QOSReserved"
+
+	// owner: @ConnorDoyle
+	// alpha: v1.8
+	// beta: v1.10
+	//
+	// Alternative container-level CPU affinity policies.
+	CPUManager featuregate.Feature = "CPUManager"
+
+	// owner: @szuecs
+	// alpha: v1.12
+	//
+	// Enable nodes to change CPUCFSQuotaPeriod
+	CPUCFSQuotaPeriod featuregate.Feature = "CustomCPUCFSQuotaPeriod"
+
+	// owner: @lmdaly
+	// alpha: v1.16
+	// beta: v1.18
+	//
+	// Enable resource managers to make NUMA aligned decisions
+	TopologyManager featuregate.Feature = "TopologyManager"
+
+	// owner: @sjenning
+	// beta: v1.11
+	//
+	// Enable pods to set sysctls on a pod
+	Sysctls featuregate.Feature = "Sysctls"
+
+	// owner @smarterclayton
+	// alpha: v1.16
+	//
+	// Enable legacy behavior to vary cluster functionality on the node-role.kubernetes.io labels. On by default (legacy), will be turned off in 1.18.
+	LegacyNodeRoleBehavior featuregate.Feature = "LegacyNodeRoleBehavior"
+
+	// owner @brendandburns
+	// alpha: v1.9
+	//
+	// Enable nodes to exclude themselves from service load balancers
+	ServiceNodeExclusion featuregate.Feature = "ServiceNodeExclusion"
+
+	// owner @smarterclayton
+	// alpha: v1.16
+	//
+	// Enable nodes to exclude themselves from network disruption checks
+	NodeDisruptionExclusion featuregate.Feature = "NodeDisruptionExclusion"
+
+	// owner: @saad-ali
+	// alpha: v1.12
+	// beta:  v1.14
+	// GA:    v1.18
+	// Enable all logic related to the CSIDriver API object in storage.k8s.io
+	CSIDriverRegistry featuregate.Feature = "CSIDriverRegistry"
+
+	// owner: @verult
+	// alpha: v1.12
+	// beta:  v1.14
+	// ga:    v1.17
+	// Enable all logic related to the CSINode API object in storage.k8s.io
+	CSINodeInfo featuregate.Feature = "CSINodeInfo"
+
+	// owner: @screeley44
+	// alpha: v1.9
+	// beta:  v1.13
+	// ga: 	  v1.18
+	//
+	// Enable Block volume support in containers.
+	BlockVolume featuregate.Feature = "BlockVolume"
+
+	// owner: @pospispa
+	// GA: v1.11
+	//
+	// Postpone deletion of a PV or a PVC when they are being used
+	StorageObjectInUseProtection featuregate.Feature = "StorageObjectInUseProtection"
+
+	// owner: @aveshagarwal
+	// alpha: v1.9
+	//
+	// Enable resource limits priority function
+	ResourceLimitsPriorityFunction featuregate.Feature = "ResourceLimitsPriorityFunction"
+
+	// owner: @m1093782566
+	// GA: v1.11
+	//
+	// Implement IPVS-based in-cluster service load balancing
+	SupportIPVSProxyMode featuregate.Feature = "SupportIPVSProxyMode"
+
+	// owner: @dims, @derekwaynecarr
+	// alpha: v1.10
+	// beta: v1.14
+	//
+	// Implement support for limiting pids in pods
+	SupportPodPidsLimit featuregate.Feature = "SupportPodPidsLimit"
+
+	// owner: @feiskyer
+	// alpha: v1.10
+	//
+	// Enable Hyper-V containers on Windows
+	HyperVContainer featuregate.Feature = "HyperVContainer"
+
+	// owner: @mikedanese
+	// beta: v1.12
+	//
+	// Implement TokenRequest endpoint on service account resources.
+	TokenRequest featuregate.Feature = "TokenRequest"
+
+	// owner: @mikedanese
+	// beta: v1.12
+	//
+	// Enable ServiceAccountTokenVolumeProjection support in ProjectedVolumes.
+	TokenRequestProjection featuregate.Feature = "TokenRequestProjection"
+
+	// owner: @mikedanese
+	// alpha: v1.13
+	//
+	// Migrate ServiceAccount volumes to use a projected volume consisting of a
+	// ServiceAccountTokenVolumeProjection. This feature adds new required flags
+	// to the API server.
+	BoundServiceAccountTokenVolume featuregate.Feature = "BoundServiceAccountTokenVolume"
+
+	// owner: @mtaufen
+	// alpha: v1.18
+	//
+	// Enable OIDC discovery endpoints (issuer and JWKS URLs) for the service
+	// account issuer in the API server.
+	// Note these endpoints serve minimally-compliant discovery docs that are
+	// intended to be used for service account token verification.
+	ServiceAccountIssuerDiscovery featuregate.Feature = "ServiceAccountIssuerDiscovery"
+
+	// owner: @Random-Liu
+	// beta: v1.11
+	//
+	// Enable container log rotation for cri container runtime
+	CRIContainerLogRotation featuregate.Feature = "CRIContainerLogRotation"
+
+	// owner: @krmayankk
+	// beta: v1.14
+	//
+	// Enables control over the primary group ID of containers' init processes.
+	RunAsGroup featuregate.Feature = "RunAsGroup"
+
+	// owner: @saad-ali
+	// ga
+	//
+	// Allow mounting a subpath of a volume in a container
+	// Do not remove this feature gate even though it's GA
+	VolumeSubpath featuregate.Feature = "VolumeSubpath"
+
+	// owner: @gnufied
+	// beta : v1.12
+	// GA   : v1.17
+
+	//
+	// Add support for volume plugins to report node specific
+	// volume limits
+	AttachVolumeLimit featuregate.Feature = "AttachVolumeLimit"
+
+	// owner: @ravig
+	// alpha: v1.11
+	//
+	// Include volume count on node to be considered for balanced resource allocation while scheduling.
+	// A node which has closer cpu,memory utilization and volume count is favoured by scheduler
+	// while making decisions.
+	BalanceAttachedNodeVolumes featuregate.Feature = "BalanceAttachedNodeVolumes"
+
+	// owner: @kevtaylor
+	// alpha: v1.14
+	// beta: v1.15
+	// ga: v1.17
+	//
+	// Allow subpath environment variable substitution
+	// Only applicable if the VolumeSubpath feature is also enabled
+	VolumeSubpathEnvExpansion featuregate.Feature = "VolumeSubpathEnvExpansion"
+
+	// owner: @vladimirvivien
+	// alpha: v1.11
+	// beta:  v1.14
+	// ga: 	  v1.18
+	//
+	// Enables CSI to use raw block storage volumes
+	CSIBlockVolume featuregate.Feature = "CSIBlockVolume"
+
+	// owner: @vladimirvivien
+	// alpha: v1.14
+	// beta: v1.16
+	//
+	// Enables CSI Inline volumes support for pods
+	CSIInlineVolume featuregate.Feature = "CSIInlineVolume"
+
+	// owner: @tallclair
+	// alpha: v1.12
+	// beta:  v1.14
+	//
+	// Enables RuntimeClass, for selecting between multiple runtimes to run a pod.
+	RuntimeClass featuregate.Feature = "RuntimeClass"
+
+	// owner: @mtaufen
+	// alpha: v1.12
+	// beta:  v1.14
+	// GA: v1.17
+	//
+	// Kubelet uses the new Lease API to report node heartbeats,
+	// (Kube) Node Lifecycle Controller uses these heartbeats as a node health signal.
+	NodeLease featuregate.Feature = "NodeLease"
+
+	// owner: @janosi
+	// alpha: v1.12
+	//
+	// Enables SCTP as new protocol for Service ports, NetworkPolicy, and ContainerPort in Pod/Containers definition
+	SCTPSupport featuregate.Feature = "SCTPSupport"
+
+	// owner: @xing-yang
+	// alpha: v1.12
+	// beta: v1.17
+	//
+	// Enable volume snapshot data source support.
+	VolumeSnapshotDataSource featuregate.Feature = "VolumeSnapshotDataSource"
+
+	// owner: @jessfraz
+	// alpha: v1.12
+	//
+	// Enables control over ProcMountType for containers.
+	ProcMountType featuregate.Feature = "ProcMountType"
+
+	// owner: @janetkuo
+	// alpha: v1.12
+	//
+	// Allow TTL controller to clean up Pods and Jobs after they finish.
+	TTLAfterFinished featuregate.Feature = "TTLAfterFinished"
+
+	// owner: @dashpole
+	// alpha: v1.13
+	// beta: v1.15
+	//
+	// Enables the kubelet's pod resources grpc endpoint
+	KubeletPodResources featuregate.Feature = "KubeletPodResources"
+
+	// owner: @davidz627
+	// alpha: v1.14
+	// beta: v1.17
+	//
+	// Enables the in-tree storage to CSI Plugin migration feature.
+	CSIMigration featuregate.Feature = "CSIMigration"
+
+	// owner: @davidz627
+	// alpha: v1.14
+	// beta: v1.17
+	//
+	// Enables the GCE PD in-tree driver to GCE CSI Driver migration feature.
+	CSIMigrationGCE featuregate.Feature = "CSIMigrationGCE"
+
+	// owner: @davidz627
+	// alpha: v1.17
+	//
+	// Disables the GCE PD in-tree driver.
+	// Expects GCE PD CSI Driver to be installed and configured on all nodes.
+	CSIMigrationGCEComplete featuregate.Feature = "CSIMigrationGCEComplete"
+
+	// owner: @leakingtapan
+	// alpha: v1.14
+	// beta: v1.17
+	//
+	// Enables the AWS EBS in-tree driver to AWS EBS CSI Driver migration feature.
+	CSIMigrationAWS featuregate.Feature = "CSIMigrationAWS"
+
+	// owner: @leakingtapan
+	// alpha: v1.17
+	//
+	// Disables the AWS EBS in-tree driver.
+	// Expects AWS EBS CSI Driver to be installed and configured on all nodes.
+	CSIMigrationAWSComplete featuregate.Feature = "CSIMigrationAWSComplete"
+
+	// owner: @andyzhangx
+	// alpha: v1.15
+	//
+	// Enables the Azure Disk in-tree driver to Azure Disk Driver migration feature.
+	CSIMigrationAzureDisk featuregate.Feature = "CSIMigrationAzureDisk"
+
+	// owner: @andyzhangx
+	// alpha: v1.17
+	//
+	// Disables the Azure Disk in-tree driver.
+	// Expects Azure Disk CSI Driver to be installed and configured on all nodes.
+	CSIMigrationAzureDiskComplete featuregate.Feature = "CSIMigrationAzureDiskComplete"
+
+	// owner: @andyzhangx
+	// alpha: v1.15
+	//
+	// Enables the Azure File in-tree driver to Azure File Driver migration feature.
+	CSIMigrationAzureFile featuregate.Feature = "CSIMigrationAzureFile"
+
+	// owner: @andyzhangx
+	// alpha: v1.17
+	//
+	// Disables the Azure File in-tree driver.
+	// Expects Azure File CSI Driver to be installed and configured on all nodes.
+	CSIMigrationAzureFileComplete featuregate.Feature = "CSIMigrationAzureFileComplete"
+
+	// owner: @gnufied
+	// alpha: v1.18
+	// Allows user to configure volume permission change policy for fsGroups when mounting
+	// a volume in a Pod.
+	ConfigurableFSGroupPolicy featuregate.Feature = "ConfigurableFSGroupPolicy"
+
+	// owner: @RobertKrawitz
+	// beta: v1.15
+	//
+	// Implement support for limiting pids in nodes
+	SupportNodePidsLimit featuregate.Feature = "SupportNodePidsLimit"
+
+	// owner: @wk8
+	// alpha: v1.14
+	// beta: v1.16
+	//
+	// Enables GMSA support for Windows workloads.
+	WindowsGMSA featuregate.Feature = "WindowsGMSA"
+
+	// owner: @bclau
+	// alpha: v1.16
+	// beta: v1.17
+	// GA: v1.18
+	//
+	// Enables support for running container entrypoints as different usernames than their default ones.
+	WindowsRunAsUserName featuregate.Feature = "WindowsRunAsUserName"
+
+	// owner: @adisky
+	// alpha: v1.14
+	// beta: v1.18
+	//
+	// Enables the OpenStack Cinder in-tree driver to OpenStack Cinder CSI Driver migration feature.
+	CSIMigrationOpenStack featuregate.Feature = "CSIMigrationOpenStack"
+
+	// owner: @adisky
+	// alpha: v1.17
+	//
+	// Disables the OpenStack Cinder in-tree driver.
+	// Expects the OpenStack Cinder CSI Driver to be installed and configured on all nodes.
+	CSIMigrationOpenStackComplete featuregate.Feature = "CSIMigrationOpenStackComplete"
+
+	// owner: @MrHohn
+	// alpha: v1.15
+	// beta:  v1.16
+	// GA: v1.17
+	//
+	// Enables Finalizer Protection for Service LoadBalancers.
+	ServiceLoadBalancerFinalizer featuregate.Feature = "ServiceLoadBalancerFinalizer"
+
+	// owner: @RobertKrawitz
+	// alpha: v1.15
+	//
+	// Allow use of filesystems for ephemeral storage monitoring.
+	// Only applies if LocalStorageCapacityIsolation is set.
+	LocalStorageCapacityIsolationFSQuotaMonitoring featuregate.Feature = "LocalStorageCapacityIsolationFSQuotaMonitoring"
+
+	// owner: @denkensk
+	// alpha: v1.15
+	//
+	// Enables NonPreempting option for priorityClass and pod.
+	NonPreemptingPriority featuregate.Feature = "NonPreemptingPriority"
+
+	// owner: @j-griffith
+	// alpha: v1.15
+	// beta: v1.16
+	// GA: v1.18
+	//
+	// Enable support for specifying an existing PVC as a DataSource
+	VolumePVCDataSource featuregate.Feature = "VolumePVCDataSource"
+
+	// owner: @egernst
+	// alpha: v1.16
+	// beta: v1.18
+	//
+	// Enables PodOverhead, for accounting pod overheads which are specific to a given RuntimeClass
+	PodOverhead featuregate.Feature = "PodOverhead"
+
+	// owner: @khenidak
+	// alpha: v1.15
+	//
+	// Enables ipv6 dual stack
+	IPv6DualStack featuregate.Feature = "IPv6DualStack"
+
+	// owner: @robscott @freehan
+	// alpha: v1.16
+	//
+	// Enable Endpoint Slices for more scalable Service endpoints.
+	EndpointSlice featuregate.Feature = "EndpointSlice"
+
+	// owner: @robscott @freehan
+	// alpha: v1.18
+	//
+	// Enable Endpoint Slice consumption by kube-proxy for improved scalability.
+	EndpointSliceProxying featuregate.Feature = "EndpointSliceProxying"
+
+	// owner: @Huang-Wei
+	// beta: v1.18
+	//
+	// Schedule pods evenly across available topology domains.
+	EvenPodsSpread featuregate.Feature = "EvenPodsSpread"
+
+	// owner: @matthyx
+	// alpha: v1.16
+	// beta: v1.18
+	//
+	// Enables the startupProbe in kubelet worker.
+	StartupProbe featuregate.Feature = "StartupProbe"
+
+	// owner: @deads2k
+	// beta: v1.17
+	//
+	// Enables the users to skip TLS verification of kubelets on pod logs requests
+	AllowInsecureBackendProxy featuregate.Feature = "AllowInsecureBackendProxy"
+
+	// owner: @mortent
+	// alpha: v1.3
+	// beta:  v1.5
+	//
+	// Enable all logic related to the PodDisruptionBudget API object in policy
+	PodDisruptionBudget featuregate.Feature = "PodDisruptionBudget"
+
+	// owner: @m1093782566
+	// alpha: v1.17
+	//
+	// Enables topology aware service routing
+	ServiceTopology featuregate.Feature = "ServiceTopology"
+
+	// owner: @robscott
+	// alpha: v1.18
+	//
+	// Enables AppProtocol field for Services and Endpoints.
+	ServiceAppProtocol featuregate.Feature = "ServiceAppProtocol"
+
+	// owner: @wojtek-t
+	// alpha: v1.18
+	//
+	// Enables a feature to make secrets and configmaps data immutable.
+	ImmutableEphemeralVolumes featuregate.Feature = "ImmutableEphemeralVolumes"
+
+	// owner: @robscott
+	// beta: v1.18
+	//
+	// Enables DefaultIngressClass admission controller.
+	DefaultIngressClass featuregate.Feature = "DefaultIngressClass"
+
+	// owner: @bart0sh
+	// alpha: v1.18
+	//
+	// Enables usage of HugePages-<size> in a volume medium,
+	// e.g. emptyDir:
+	//        medium: HugePages-1Gi
+	HugePageStorageMediumSize featuregate.Feature = "HugePageStorageMediumSize"
+
+	// owner: @freehan
+	// GA: v1.18
+	//
+	// Enable ExternalTrafficPolicy for Service ExternalIPs.
+	// This is for bug fix #69811
+	ExternalPolicyForExternalIP featuregate.Feature = "ExternalPolicyForExternalIP"
+
+	// owner: @bswartz
+	// alpha: v1.18
+	//
+	// Enables usage of any object for volume data source in PVCs
+	AnyVolumeDataSource featuregate.Feature = "AnyVolumeDataSource"
+)
+
+func init() {
+	runtime.Must(utilfeature.DefaultMutableFeatureGate.Add(defaultKubernetesFeatureGates))
+}
+
+// defaultKubernetesFeatureGates consists of all known Kubernetes-specific feature keys.
+// To add a new feature, define a key for it above and add it here. The features will be
+// available throughout Kubernetes binaries.
+var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
+	AppArmor:             {Default: true, PreRelease: featuregate.Beta},
+	DynamicKubeletConfig: {Default: true, PreRelease: featuregate.Beta},
+	ExperimentalHostUserNamespaceDefaultingGate: {Default: false, PreRelease: featuregate.Beta},
+	DevicePlugins:                  {Default: true, PreRelease: featuregate.Beta},
+	TaintBasedEvictions:            {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.19
+	RotateKubeletServerCertificate: {Default: true, PreRelease: featuregate.Beta},
+	RotateKubeletClientCertificate: {Default: true, PreRelease: featuregate.Beta},
+	LocalStorageCapacityIsolation:  {Default: true, PreRelease: featuregate.Beta},
+	Sysctls:                        {Default: true, PreRelease: featuregate.Beta},
+	EphemeralContainers:            {Default: false, PreRelease: featuregate.Alpha},
+	PodShareProcessNamespace:       {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.19
+	QOSReserved:                    {Default: false, PreRelease: featuregate.Alpha},
+	ExpandPersistentVolumes:        {Default: true, PreRelease: featuregate.Beta},
+	ExpandInUsePersistentVolumes:   {Default: true, PreRelease: featuregate.Beta},
+	ExpandCSIVolumes:               {Default: true, PreRelease: featuregate.Beta},
+	AttachVolumeLimit:              {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.19
+	CPUManager:                     {Default: true, PreRelease: featuregate.Beta},
+	CPUCFSQuotaPeriod:              {Default: false, PreRelease: featuregate.Alpha},
+	TopologyManager:                {Default: true, PreRelease: featuregate.Beta},
+	ServiceNodeExclusion:           {Default: false, PreRelease: featuregate.Alpha},
+	NodeDisruptionExclusion:        {Default: false, PreRelease: featuregate.Alpha},
+	CSIDriverRegistry:              {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
+	CSINodeInfo:                    {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.19
+	BlockVolume:                    {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
+	StorageObjectInUseProtection:   {Default: true, PreRelease: featuregate.GA},
+	ResourceLimitsPriorityFunction: {Default: false, PreRelease: featuregate.Alpha},
+	SupportIPVSProxyMode:           {Default: true, PreRelease: featuregate.GA},
+	SupportPodPidsLimit:            {Default: true, PreRelease: featuregate.Beta},
+	SupportNodePidsLimit:           {Default: true, PreRelease: featuregate.Beta},
+	HyperVContainer:                {Default: false, PreRelease: featuregate.Alpha},
+	TokenRequest:                   {Default: true, PreRelease: featuregate.Beta},
+	TokenRequestProjection:         {Default: true, PreRelease: featuregate.Beta},
+	BoundServiceAccountTokenVolume: {Default: false, PreRelease: featuregate.Alpha},
+	ServiceAccountIssuerDiscovery:  {Default: false, PreRelease: featuregate.Alpha},
+	CRIContainerLogRotation:        {Default: true, PreRelease: featuregate.Beta},
+	CSIMigration:                   {Default: true, PreRelease: featuregate.Beta},
+	CSIMigrationGCE:                {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires GCE PD CSI Driver)
+	CSIMigrationGCEComplete:        {Default: false, PreRelease: featuregate.Alpha},
+	CSIMigrationAWS:                {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires AWS EBS CSI driver)
+	CSIMigrationAWSComplete:        {Default: false, PreRelease: featuregate.Alpha},
+	CSIMigrationAzureDisk:          {Default: false, PreRelease: featuregate.Alpha},
+	CSIMigrationAzureDiskComplete:  {Default: false, PreRelease: featuregate.Alpha},
+	CSIMigrationAzureFile:          {Default: false, PreRelease: featuregate.Alpha},
+	CSIMigrationAzureFileComplete:  {Default: false, PreRelease: featuregate.Alpha},
+	RunAsGroup:                     {Default: true, PreRelease: featuregate.Beta},
+	CSIMigrationOpenStack:          {Default: false, PreRelease: featuregate.Beta}, // Off by default (requires OpenStack Cinder CSI driver)
+	CSIMigrationOpenStackComplete:  {Default: false, PreRelease: featuregate.Alpha},
+	VolumeSubpath:                  {Default: true, PreRelease: featuregate.GA},
+	ConfigurableFSGroupPolicy:      {Default: false, PreRelease: featuregate.Alpha},
+	BalanceAttachedNodeVolumes:     {Default: false, PreRelease: featuregate.Alpha},
+	VolumeSubpathEnvExpansion:      {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.19,
+	CSIBlockVolume:                 {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
+	CSIInlineVolume:                {Default: true, PreRelease: featuregate.Beta},
+	RuntimeClass:                   {Default: true, PreRelease: featuregate.Beta},
+	NodeLease:                      {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
+	SCTPSupport:                    {Default: false, PreRelease: featuregate.Alpha},
+	VolumeSnapshotDataSource:       {Default: true, PreRelease: featuregate.Beta},
+	ProcMountType:                  {Default: false, PreRelease: featuregate.Alpha},
+	TTLAfterFinished:               {Default: false, PreRelease: featuregate.Alpha},
+	KubeletPodResources:            {Default: true, PreRelease: featuregate.Beta},
+	WindowsGMSA:                    {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
+	WindowsRunAsUserName:           {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
+	ServiceLoadBalancerFinalizer:   {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
+	LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha},
+	NonPreemptingPriority:                          {Default: false, PreRelease: featuregate.Alpha},
+	VolumePVCDataSource:                            {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
+	PodOverhead:                                    {Default: true, PreRelease: featuregate.Beta},
+	IPv6DualStack:                                  {Default: false, PreRelease: featuregate.Alpha},
+	EndpointSlice:                                  {Default: true, PreRelease: featuregate.Beta},
+	EndpointSliceProxying:                          {Default: false, PreRelease: featuregate.Alpha},
+	EvenPodsSpread:                                 {Default: true, PreRelease: featuregate.Beta},
+	StartupProbe:                                   {Default: true, PreRelease: featuregate.Beta},
+	AllowInsecureBackendProxy:                      {Default: true, PreRelease: featuregate.Beta},
+	PodDisruptionBudget:                            {Default: true, PreRelease: featuregate.Beta},
+	ServiceTopology:                                {Default: false, PreRelease: featuregate.Alpha},
+	ServiceAppProtocol:                             {Default: false, PreRelease: featuregate.Alpha},
+	ImmutableEphemeralVolumes:                      {Default: false, PreRelease: featuregate.Alpha},
+	DefaultIngressClass:                            {Default: true, PreRelease: featuregate.Beta},
+	HugePageStorageMediumSize:                      {Default: false, PreRelease: featuregate.Alpha},
+	ExternalPolicyForExternalIP:                    {Default: false, PreRelease: featuregate.GA}, // remove in 1.19
+	AnyVolumeDataSource:                            {Default: false, PreRelease: featuregate.Alpha},
+
+	// inherited features from generic apiserver, relisted here to get a conflict if it is changed
+	// unintentionally on either side:
+	genericfeatures.StreamingProxyRedirects: {Default: true, PreRelease: featuregate.Deprecated},
+	genericfeatures.ValidateProxyRedirects:  {Default: true, PreRelease: featuregate.Beta},
+	genericfeatures.AdvancedAuditing:        {Default: true, PreRelease: featuregate.GA},
+	genericfeatures.DynamicAuditing:         {Default: false, PreRelease: featuregate.Alpha},
+	genericfeatures.APIResponseCompression:  {Default: true, PreRelease: featuregate.Beta},
+	genericfeatures.APIListChunking:         {Default: true, PreRelease: featuregate.Beta},
+	genericfeatures.DryRun:                  {Default: true, PreRelease: featuregate.Beta},
+	genericfeatures.ServerSideApply:         {Default: true, PreRelease: featuregate.Beta},
+	genericfeatures.APIPriorityAndFairness:  {Default: false, PreRelease: featuregate.Alpha},
+
+	// features that enable backwards compatibility but are scheduled to be removed
+	// ...
+	HPAScaleToZero:         {Default: false, PreRelease: featuregate.Alpha},
+	LegacyNodeRoleBehavior: {Default: true, PreRelease: featuregate.Alpha},
+}
diff --git a/pkg/controller/kubernetes/util/node/node.go b/pkg/controller/kubernetes/util/node/node.go
new file mode 100644
index 00000000..97a10970
--- /dev/null
+++ b/pkg/controller/kubernetes/util/node/node.go
@@ -0,0 +1,66 @@
+/*
+Copyright 2015 The Kubernetes Authors.
+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 node
+
+import (
+	v1 "k8s.io/api/core/v1"
+)
+
+const (
+	// NodeUnreachablePodReason is the reason on a pod when its state cannot be confirmed as kubelet is unresponsive
+	// on the node it is (was) running.
+	NodeUnreachablePodReason = "NodeLost"
+	// NodeUnreachablePodMessage is the message on a pod when its state cannot be confirmed as kubelet is unresponsive
+	// on the node it is (was) running.
+	NodeUnreachablePodMessage = "Node %v which was running pod %v is unresponsive"
+)
+
+// GetZoneKey is a helper function that builds a string identifier that is unique per failure-zone;
+// it returns empty-string for no zone.
+// Since there are currently two separate zone keys:
+//   * "failure-domain.beta.kubernetes.io/zone"
+//   * "topology.kubernetes.io/zone"
+// GetZoneKey will first check failure-domain.beta.kubernetes.io/zone and if not exists, will then check
+// topology.kubernetes.io/zone
+func GetZoneKey(node *v1.Node) string {
+	labels := node.Labels
+	if labels == nil {
+		return ""
+	}
+
+	// TODO: prefer stable labels for zone in v1.18
+	zone, ok := labels[v1.LabelZoneFailureDomain]
+	if !ok {
+		zone, _ = labels[v1.LabelZoneFailureDomainStable]
+	}
+
+	// TODO: prefer stable labels for region in v1.18
+	region, ok := labels[v1.LabelZoneRegion]
+	if !ok {
+		region, _ = labels[v1.LabelZoneRegionStable]
+	}
+
+	if region == "" && zone == "" {
+		return ""
+	}
+
+	// We include the null character just in case region or failureDomain has a colon
+	// (We do assume there's no null characters in a region or failureDomain)
+	// As a nice side-benefit, the null character is not printed by fmt.Print or glog
+	return region + ":\x00:" + zone
+}
diff --git a/pkg/controller/kubernetes/util/taints/taints.go b/pkg/controller/kubernetes/util/taints/taints.go
new file mode 100644
index 00000000..5cccafd5
--- /dev/null
+++ b/pkg/controller/kubernetes/util/taints/taints.go
@@ -0,0 +1,125 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+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 taints implements utilities for working with taints
+package taints
+
+import (
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
+)
+
+// DeleteTaint removes all the taints that have the same key and effect to given taintToDelete.
+func DeleteTaint(taints []v1.Taint, taintToDelete *v1.Taint) ([]v1.Taint, bool) {
+	newTaints := []v1.Taint{}
+	deleted := false
+	for i := range taints {
+		if taintToDelete.MatchTaint(&taints[i]) {
+			deleted = true
+			continue
+		}
+		newTaints = append(newTaints, taints[i])
+	}
+	return newTaints, deleted
+}
+
+// RemoveTaint tries to remove a taint from annotations list. Returns a new copy of updated Node and true if something was updated
+// false otherwise.
+func RemoveTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool, error) {
+	newNode := node.DeepCopy()
+	nodeTaints := newNode.Spec.Taints
+	if len(nodeTaints) == 0 {
+		return newNode, false, nil
+	}
+
+	if !TaintExists(nodeTaints, taint) {
+		return newNode, false, nil
+	}
+
+	newTaints, _ := DeleteTaint(nodeTaints, taint)
+	newNode.Spec.Taints = newTaints
+	return newNode, true, nil
+}
+
+// AddOrUpdateTaint tries to add a taint to annotations list. Returns a new copy of updated Node and true if something was updated
+// false otherwise.
+func AddOrUpdateTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool, error) {
+	newNode := node.DeepCopy()
+	nodeTaints := newNode.Spec.Taints
+
+	var newTaints []v1.Taint
+	updated := false
+	for i := range nodeTaints {
+		if taint.MatchTaint(&nodeTaints[i]) {
+			if equality.Semantic.DeepEqual(*taint, nodeTaints[i]) {
+				return newNode, false, nil
+			}
+			newTaints = append(newTaints, *taint)
+			updated = true
+			continue
+		}
+
+		newTaints = append(newTaints, nodeTaints[i])
+	}
+
+	if !updated {
+		newTaints = append(newTaints, *taint)
+	}
+
+	newNode.Spec.Taints = newTaints
+	return newNode, true, nil
+}
+
+// TaintExists checks if the given taint exists in list of taints. Returns true if exists false otherwise.
+func TaintExists(taints []v1.Taint, taintToFind *v1.Taint) bool {
+	for _, taint := range taints {
+		if taint.MatchTaint(taintToFind) {
+			return true
+		}
+	}
+	return false
+}
+
+func TaintSetDiff(t1, t2 []v1.Taint) (taintsToAdd []*v1.Taint, taintsToRemove []*v1.Taint) {
+	for _, taint := range t1 {
+		if !TaintExists(t2, &taint) {
+			t := taint
+			taintsToAdd = append(taintsToAdd, &t)
+		}
+	}
+
+	for _, taint := range t2 {
+		if !TaintExists(t1, &taint) {
+			t := taint
+			taintsToRemove = append(taintsToRemove, &t)
+		}
+	}
+
+	return
+}
+
+func TaintSetFilter(taints []v1.Taint, fn func(*v1.Taint) bool) []v1.Taint {
+	res := []v1.Taint{}
+
+	for _, taint := range taints {
+		if fn(&taint) {
+			res = append(res, taint)
+		}
+	}
+
+	return res
+}
diff --git a/pkg/controller/nodelifecycle/node_lifecycle_controller.go b/pkg/controller/nodelifecycle/node_lifecycle_controller.go
index ac5c8206..9ca0c083 100644
--- a/pkg/controller/nodelifecycle/node_lifecycle_controller.go
+++ b/pkg/controller/nodelifecycle/node_lifecycle_controller.go
@@ -53,12 +53,11 @@ import (
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/component-base/metrics/prometheus/ratelimiter"
 	"k8s.io/klog/v2"
-	"k8s.io/kubernetes/pkg/controller"
-	kubefeatures "k8s.io/kubernetes/pkg/features"
-	kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
-	utilnode "k8s.io/kubernetes/pkg/util/node"
-	taintutils "k8s.io/kubernetes/pkg/util/taints"
 
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller"
+	kubefeatures "github.com/openyurtio/openyurt/pkg/controller/kubernetes/features"
+	utilnode "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/node"
+	taintutils "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/taints"
 	"github.com/openyurtio/openyurt/pkg/controller/nodelifecycle/scheduler"
 	nodeutil "github.com/openyurtio/openyurt/pkg/controller/util/node"
 )
@@ -68,6 +67,15 @@ func init() {
 	Register()
 }
 
+const (
+	// LabelOS is a label to indicate the operating system of the node.
+	// The OS labels are promoted to GA in 1.14. kubelet applies GA labels and stop applying the beta OS labels in Kubernetes 1.19.
+	LabelOS = "beta.kubernetes.io/os"
+	// LabelArch is a label to indicate the architecture of the node.
+	// The Arch labels are promoted to GA in 1.14. kubelet applies GA labels and stop applying the beta Arch labels in Kubernetes 1.19.
+	LabelArch = "beta.kubernetes.io/arch"
+)
+
 var (
 	// UnreachableTaintTemplate is the taint for when a node becomes unreachable.
 	UnreachableTaintTemplate = &v1.Taint{
@@ -150,7 +158,7 @@ var labelReconcileInfo = []struct {
 		// the source of truth.
 		// TODO(#73084): switch to using the stable label as the source of
 		// truth in v1.18.
-		primaryKey:            kubeletapis.LabelOS,
+		primaryKey:            LabelOS,
 		secondaryKey:          v1.LabelOSStable,
 		ensureSecondaryExists: true,
 	},
@@ -159,7 +167,7 @@ var labelReconcileInfo = []struct {
 		// the source of truth.
 		// TODO(#73084): switch to using the stable label as the source of
 		// truth in v1.18.
-		primaryKey:            kubeletapis.LabelArch,
+		primaryKey:            LabelArch,
 		secondaryKey:          v1.LabelArchStable,
 		ensureSecondaryExists: true,
 	},
diff --git a/pkg/controller/nodelifecycle/scheduler/taint_manager.go b/pkg/controller/nodelifecycle/scheduler/taint_manager.go
index ed26f8c6..34a7e023 100644
--- a/pkg/controller/nodelifecycle/scheduler/taint_manager.go
+++ b/pkg/controller/nodelifecycle/scheduler/taint_manager.go
@@ -27,6 +27,7 @@ import (
 	"time"
 
 	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/equality"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
@@ -37,8 +38,6 @@ import (
 	"k8s.io/client-go/tools/record"
 	"k8s.io/client-go/util/workqueue"
 	"k8s.io/klog/v2"
-	"k8s.io/kubernetes/pkg/apis/core/helper"
-	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
 
 	nodeutil "github.com/openyurtio/openyurt/pkg/controller/util/node"
 )
@@ -299,7 +298,7 @@ func (tc *NoExecuteTaintManager) PodUpdated(oldPod *v1.Pod, newPod *v1.Pod) {
 		newTolerations = newPod.Spec.Tolerations
 	}
 
-	if oldPod != nil && newPod != nil && helper.Semantic.DeepEqual(oldTolerations, newTolerations) && oldPod.Spec.NodeName == newPod.Spec.NodeName {
+	if oldPod != nil && newPod != nil && equality.Semantic.DeepEqual(oldTolerations, newTolerations) && oldPod.Spec.NodeName == newPod.Spec.NodeName {
 		return
 	}
 	updateItem := podUpdateItem{
@@ -326,7 +325,7 @@ func (tc *NoExecuteTaintManager) NodeUpdated(oldNode *v1.Node, newNode *v1.Node)
 		newTaints = getNoExecuteTaints(newNode.Spec.Taints)
 	}
 
-	if oldNode != nil && newNode != nil && helper.Semantic.DeepEqual(oldTaints, newTaints) {
+	if oldNode != nil && newNode != nil && equality.Semantic.DeepEqual(oldTaints, newTaints) {
 		return
 	}
 
@@ -367,7 +366,7 @@ func (tc *NoExecuteTaintManager) processPodOnNode(
 	if len(taints) == 0 {
 		tc.cancelWorkWithEvent(podNamespacedName)
 	}
-	allTolerated, usedTolerations := v1helper.GetMatchingTolerations(taints, tolerations)
+	allTolerated, usedTolerations := GetMatchingTolerations(taints, tolerations)
 	if !allTolerated {
 		klog.V(2).Infof("Not all taints are tolerated after update for Pod %v on %v", podNamespacedName.String(), nodeName)
 		// We're canceling scheduled work (if any), as we're going to delete the Pod right away.
@@ -514,3 +513,28 @@ func (tc *NoExecuteTaintManager) emitCancelPodDeletionEvent(nsName types.Namespa
 	}
 	tc.recorder.Eventf(ref, v1.EventTypeNormal, "TaintManagerEviction", "Cancelling deletion of Pod %s", nsName.String())
 }
+
+// GetMatchingTolerations Returns true and list of Tolerations matching all Taints if all are tolerated, or false otherwise.
+func GetMatchingTolerations(taints []v1.Taint, tolerations []v1.Toleration) (bool, []v1.Toleration) {
+	if len(taints) == 0 {
+		return true, []v1.Toleration{}
+	}
+	if len(tolerations) == 0 && len(taints) > 0 {
+		return false, []v1.Toleration{}
+	}
+	result := []v1.Toleration{}
+	for i := range taints {
+		tolerated := false
+		for j := range tolerations {
+			if tolerations[j].ToleratesTaint(&taints[i]) {
+				result = append(result, tolerations[j])
+				tolerated = true
+				break
+			}
+		}
+		if !tolerated {
+			return false, []v1.Toleration{}
+		}
+	}
+	return true, result
+}
diff --git a/pkg/controller/util/node/controller_utils.go b/pkg/controller/util/node/controller_utils.go
index 99b9c3c3..45e716ee 100644
--- a/pkg/controller/util/node/controller_utils.go
+++ b/pkg/controller/util/node/controller_utils.go
@@ -33,11 +33,10 @@ import (
 	"k8s.io/client-go/tools/cache"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/klog/v2"
-	utilpod "k8s.io/kubernetes/pkg/api/v1/pod"
-	"k8s.io/kubernetes/pkg/controller"
-	"k8s.io/kubernetes/pkg/kubelet/util/format"
-	nodepkg "k8s.io/kubernetes/pkg/util/node"
 
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller"
+	ctlnode "github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller/util/node"
+	nodepkg "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/node"
 	"github.com/openyurtio/openyurt/pkg/projectinfo"
 )
 
@@ -69,7 +68,7 @@ func DeletePods(kubeClient clientset.Interface, pods []*v1.Pod, recorder record.
 		if _, err := SetPodTerminationReason(kubeClient, pod, nodeName); err != nil {
 			if apierrors.IsConflict(err) {
 				updateErrList = append(updateErrList,
-					fmt.Errorf("update status failed for pod %q: %v", format.Pod(pod), err))
+					fmt.Errorf("update status failed for pod %q: %v", ctlnode.Pod(pod), err))
 				continue
 			}
 		}
@@ -144,7 +143,7 @@ func MarkPodsNotReady(kubeClient clientset.Interface, pods []*v1.Pod, nodeName s
 		for _, cond := range pod.Status.Conditions {
 			if cond.Type == v1.PodReady {
 				cond.Status = v1.ConditionFalse
-				if !utilpod.UpdatePodCondition(&pod.Status, &cond) {
+				if !UpdatePodCondition(&pod.Status, &cond) {
 					break
 				}
 				klog.V(2).Infof("Updating ready status of pod %v to false", pod.Name)
@@ -155,7 +154,7 @@ func MarkPodsNotReady(kubeClient clientset.Interface, pods []*v1.Pod, nodeName s
 						// There is nothing left to do with this pod.
 						continue
 					}
-					klog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err)
+					klog.Warningf("Failed to update status for pod %q: %v", ctlnode.Pod(pod), err)
 					errMsg = append(errMsg, fmt.Sprintf("%v", err))
 				}
 				break
@@ -306,3 +305,55 @@ func GetNodeCondition(status *v1.NodeStatus, conditionType v1.NodeConditionType)
 	}
 	return -1, nil
 }
+
+// GetPodCondition extracts the provided condition from the given status and returns that.
+// Returns nil and -1 if the condition is not present, and the index of the located condition.
+func GetPodCondition(status *v1.PodStatus, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
+	if status == nil {
+		return -1, nil
+	}
+	return GetPodConditionFromList(status.Conditions, conditionType)
+}
+
+// GetPodConditionFromList extracts the provided condition from the given list of condition and
+// returns the index of the condition and the condition. Returns -1 and nil if the condition is not present.
+func GetPodConditionFromList(conditions []v1.PodCondition, conditionType v1.PodConditionType) (int, *v1.PodCondition) {
+	if conditions == nil {
+		return -1, nil
+	}
+	for i := range conditions {
+		if conditions[i].Type == conditionType {
+			return i, &conditions[i]
+		}
+	}
+	return -1, nil
+}
+
+// UpdatePodCondition updates existing pod condition or creates a new one. Sets LastTransitionTime to now if the
+// status has changed.
+// Returns true if pod condition has changed or has been added.
+func UpdatePodCondition(status *v1.PodStatus, condition *v1.PodCondition) bool {
+	condition.LastTransitionTime = metav1.Now()
+	// Try to find this pod condition.
+	conditionIndex, oldCondition := GetPodCondition(status, condition.Type)
+
+	if oldCondition == nil {
+		// We are adding new pod condition.
+		status.Conditions = append(status.Conditions, *condition)
+		return true
+	}
+	// We are updating an existing condition, so we need to check if it has changed.
+	if condition.Status == oldCondition.Status {
+		condition.LastTransitionTime = oldCondition.LastTransitionTime
+	}
+
+	isEqual := condition.Status == oldCondition.Status &&
+		condition.Reason == oldCondition.Reason &&
+		condition.Message == oldCondition.Message &&
+		condition.LastProbeTime.Equal(&oldCondition.LastProbeTime) &&
+		condition.LastTransitionTime.Equal(&oldCondition.LastTransitionTime)
+
+	status.Conditions[conditionIndex] = *condition
+	// Return true if one of the fields have changed.
+	return !isEqual
+}
diff --git a/pkg/yurthub/certificate/hubself/cert_mgr.go b/pkg/yurthub/certificate/hubself/cert_mgr.go
index 9234c245..b9991ca0 100644
--- a/pkg/yurthub/certificate/hubself/cert_mgr.go
+++ b/pkg/yurthub/certificate/hubself/cert_mgr.go
@@ -23,7 +23,6 @@ import (
 	"crypto/x509/pkix"
 	"fmt"
 	"io/ioutil"
-	"net"
 	"net/url"
 	"path/filepath"
 	"strings"
@@ -71,21 +70,20 @@ func Register(cmr *hubcert.CertificateManagerRegistry) {
 }
 
 type yurtHubCertManager struct {
-	remoteServers              []*url.URL
-	hubCertOrganizations       []string
-	proxyServerSecureDummyHost string
-	bootstrapConfStore         storage.Store
-	hubClientCertManager       certificate.Manager
-	hubClientCertPath          string
-	joinToken                  string
-	caFile                     string
-	nodeName                   string
-	rootDir                    string
-	hubName                    string
-	kubeletRootCAFilePath      string
-	kubeletPairFilePath        string
-	dialer                     *util.Dialer
-	stopCh                     chan struct{}
+	remoteServers         []*url.URL
+	hubCertOrganizations  []string
+	bootstrapConfStore    storage.Store
+	hubClientCertManager  certificate.Manager
+	hubClientCertPath     string
+	joinToken             string
+	caFile                string
+	nodeName              string
+	rootDir               string
+	hubName               string
+	kubeletRootCAFilePath string
+	kubeletPairFilePath   string
+	dialer                *util.Dialer
+	stopCh                chan struct{}
 }
 
 // NewYurtHubCertManager new a YurtCertificateManager instance
@@ -94,11 +92,6 @@ func NewYurtHubCertManager(cfg *config.YurtHubConfiguration) (interfaces.YurtCer
 		return nil, fmt.Errorf("hub agent configuration is invalid, could not new hub agent cert manager")
 	}
 
-	proxyServerSecureDummyHost, _, err := net.SplitHostPort(cfg.YurtHubProxyServerSecureDummyAddr)
-	if err != nil {
-		return nil, fmt.Errorf("hub agent proxyServerSecureDummyHost is invalid, could not new hub agent cert manager: %v", err)
-	}
-
 	hn := projectinfo.GetHubName()
 	if len(hn) == 0 {
 		hn = HubName
@@ -110,17 +103,16 @@ func NewYurtHubCertManager(cfg *config.YurtHubConfiguration) (interfaces.YurtCer
 	}
 
 	ycm := &yurtHubCertManager{
-		remoteServers:              cfg.RemoteServers,
-		hubCertOrganizations:       cfg.YurtHubCertOrganizations,
-		proxyServerSecureDummyHost: proxyServerSecureDummyHost,
-		nodeName:                   cfg.NodeName,
-		joinToken:                  cfg.JoinToken,
-		kubeletRootCAFilePath:      cfg.KubeletRootCAFilePath,
-		kubeletPairFilePath:        cfg.KubeletPairFilePath,
-		rootDir:                    rootDir,
-		hubName:                    hn,
-		dialer:                     util.NewDialer("hub certificate manager"),
-		stopCh:                     make(chan struct{}),
+		remoteServers:         cfg.RemoteServers,
+		hubCertOrganizations:  cfg.YurtHubCertOrganizations,
+		nodeName:              cfg.NodeName,
+		joinToken:             cfg.JoinToken,
+		kubeletRootCAFilePath: cfg.KubeletRootCAFilePath,
+		kubeletPairFilePath:   cfg.KubeletPairFilePath,
+		rootDir:               rootDir,
+		hubName:               hn,
+		dialer:                util.NewDialer("hub certificate manager"),
+		stopCh:                make(chan struct{}),
 	}
 
 	return ycm, nil
@@ -301,11 +293,11 @@ func (ycm *yurtHubCertManager) initBootstrap() error {
 	}
 }
 
-// initClientCertificateManager init hub agent certificate manager
+// initClientCertificateManager init hub client certificate manager
 func (ycm *yurtHubCertManager) initClientCertificateManager() error {
 	s, err := certificate.NewFileStore(ycm.hubName, ycm.getPkiDir(), ycm.getPkiDir(), "", "")
 	if err != nil {
-		klog.Errorf("failed to init %s cert store, %v", ycm.hubName, err)
+		klog.Errorf("failed to init %s client cert store, %v", ycm.hubName, err)
 		return err
 
 	}
@@ -328,18 +320,17 @@ func (ycm *yurtHubCertManager) initClientCertificateManager() error {
 				CommonName:   fmt.Sprintf("system:node:%s", ycm.nodeName),
 				Organization: orgs,
 			},
-			IPAddresses: []net.IP{net.ParseIP("127.0.0.1"), net.ParseIP(ycm.proxyServerSecureDummyHost)},
 		},
 		Usages: []certificates.KeyUsage{
 			certificates.UsageDigitalSignature,
 			certificates.UsageKeyEncipherment,
-			certificates.UsageServerAuth,
 			certificates.UsageClientAuth,
 		},
+
 		CertificateStore: s,
 	})
 	if err != nil {
-		return fmt.Errorf("failed to initialize %s certificate manager: %v", ycm.hubName, err)
+		return fmt.Errorf("failed to initialize client certificate manager: %v", err)
 	}
 	ycm.hubClientCertManager = m
 	m.Start()
diff --git a/pkg/yurthub/certificate/pki.go b/pkg/yurthub/certificate/pki.go
index 3a5a1767..e3097894 100644
--- a/pkg/yurthub/certificate/pki.go
+++ b/pkg/yurthub/certificate/pki.go
@@ -23,92 +23,32 @@ import (
 	"fmt"
 	"io/ioutil"
 	"os"
-	"time"
 
 	"k8s.io/client-go/util/certificate"
-	"k8s.io/klog/v2"
-
-	"github.com/openyurtio/openyurt/pkg/projectinfo"
-	"github.com/openyurtio/openyurt/pkg/yurthub/certificate/interfaces"
 )
 
-// hubServerCertMgr is the certificate manager for server usage, which will cache the old hub-server-certificate,
-type hubServerCertMgr struct {
-	// the path of old hub server certificate
-	oldCertFile string
-	// the cached old hub server certificate
-	oldCert *tls.Certificate
-	// the actual hub certMgr
-	hubCertMgr certificate.Manager
-}
-
-// newHubServerCertMgr creates a certificate manager for the yurthub-server
-func newHubServerCertMgr(certDir string, hubCertMgr certificate.Manager) hubServerCertMgr {
-	var oldCertFile string
-	var oldCert *tls.Certificate
-
-	store, err := certificate.NewFileStore(fmt.Sprintf("%s-server", projectinfo.GetHubName()), certDir, certDir, "", "")
-	if err != nil {
-		oldCertFile = ""
-		oldCert = nil
-	} else {
-		oldCertFile = store.CurrentPath()
-		oldCert, err = store.Current()
-		if err != nil {
-			oldCert = nil
-		}
-	}
-
-	return hubServerCertMgr{
-		oldCertFile: oldCertFile,
-		oldCert:     oldCert,
-		hubCertMgr:  hubCertMgr,
-	}
-}
-
-// Current is used to obtain the certificate for hub server usage.
-// If the current certificate of hub certMgr is invalid (if the key usages of certificate doesn't contain the UsageServerAuth),
-// the old certificate in the yurthub-server-current.pem will be used.
-func (scm *hubServerCertMgr) Current() *tls.Certificate {
-	cert := scm.hubCertMgr.Current()
-	if cert == nil {
-		return &tls.Certificate{Certificate: nil}
-	}
-
-	if hasServerAuthUsage(cert) {
-		return cert
-	}
-
-	klog.V(3).Infof("the %s certificate is not valid for server, try to use the old server certificate in %s",
-		projectinfo.GetHubName(), scm.oldCertFile)
-	if scm.oldCert != nil && time.Now().Before(scm.oldCert.Leaf.NotAfter) {
-		return scm.oldCert
-	} else {
-		klog.V(3).Infof("failed to get the old %s server certificate, wait for the new hub certificate", projectinfo.GetHubName())
-	}
-	return &tls.Certificate{Certificate: nil}
-}
-
-// GenTLSConfigUseCertMgr generates a TLS configuration by using the given certificate manager
-func GenTLSConfigUseCertMgr(m interfaces.YurtCertificateManager, certDir string) (*tls.Config, error) {
-	// generate the TLS configuration based on the latest certificate
-	rootCert, err := GenCertPoolUseCA(m.GetCaFile())
-	if err != nil {
-		klog.Errorf("could not generate a x509 CertPool based on the given CA file, %v", err)
-		return nil, err
-	}
-
+// GenTGenTLSConfigUseCertMgrAndCertPool generates a TLS configuration
+// using the given certificate manager and x509 CertPool
+func GenTLSConfigUseCertMgrAndCertPool(
+	m certificate.Manager,
+	root *x509.CertPool) (*tls.Config, error) {
 	tlsConfig := &tls.Config{
 		// Can't use SSLv3 because of POODLE and BEAST
 		// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
 		// Can't use TLSv1.1 because of RC4 cipher usage
 		MinVersion: tls.VersionTLS12,
-		ClientCAs:  rootCert,
+		ClientCAs:  root,
 		ClientAuth: tls.VerifyClientCertIfGiven,
 	}
 
-	scm := newHubServerCertMgr(certDir, m)
-	tlsConfig.GetCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { return scm.Current(), nil }
+	tlsConfig.GetCertificate =
+		func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
+			cert := m.Current()
+			if cert == nil {
+				return &tls.Certificate{Certificate: nil}, nil
+			}
+			return cert, nil
+		}
 
 	return tlsConfig, nil
 }
@@ -135,17 +75,3 @@ func GenCertPoolUseCA(caFile string) (*x509.CertPool, error) {
 	certPool.AppendCertsFromPEM(caData)
 	return certPool, nil
 }
-
-// hasServerAuthUsage checks whether the certificate is valid or not
-func hasServerAuthUsage(cert *tls.Certificate) bool {
-	if cert == nil {
-		return false
-	}
-	// if the key usages of certificate doesn't contain the UsageServerAuth, it means the cert is invalid
-	for _, v := range cert.Leaf.ExtKeyUsage {
-		if v == x509.ExtKeyUsageServerAuth {
-			return true
-		}
-	}
-	return false
-}
diff --git a/pkg/yurthub/certificate/server/certmanager.go b/pkg/yurthub/certificate/server/certmanager.go
new file mode 100644
index 00000000..72aed5d0
--- /dev/null
+++ b/pkg/yurthub/certificate/server/certmanager.go
@@ -0,0 +1,106 @@
+/*
+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 server
+
+import (
+	"crypto/tls"
+	"crypto/x509"
+	"crypto/x509/pkix"
+	"fmt"
+	"net"
+
+	certificates "k8s.io/api/certificates/v1beta1"
+	"k8s.io/client-go/kubernetes"
+	clicert "k8s.io/client-go/kubernetes/typed/certificates/v1beta1"
+	"k8s.io/client-go/util/certificate"
+	"k8s.io/klog/v2"
+
+	"github.com/openyurtio/openyurt/pkg/projectinfo"
+)
+
+const (
+	YurtHubServerCSROrg = "system:masters"
+	YurtHubCSROrg       = "openyurt:yurthub"
+	YurtHubServerCSRCN  = "kube-apiserver-kubelet-client"
+)
+
+// NewYurtHubServerCertManager creates a certificate manager for
+// the yurthub-server
+func NewYurtHubServerCertManager(
+	clientset kubernetes.Interface,
+	certDir,
+	proxyServerSecureDummyAddr string) (certificate.Manager, error) {
+
+	klog.Infof("subject of yurthub server certificate")
+	host, _, err := net.SplitHostPort(proxyServerSecureDummyAddr)
+	if err != nil {
+		return nil, err
+	}
+
+	return newCertManager(
+		clientset,
+		fmt.Sprintf("%s-server", projectinfo.GetHubName()),
+		certDir,
+		YurtHubServerCSRCN,
+		[]string{YurtHubServerCSROrg, YurtHubCSROrg},
+		[]net.IP{net.ParseIP("127.0.0.1"), net.ParseIP(host)})
+}
+
+// NewCertManager creates a certificate manager that will generates a
+// certificate by sending a csr to the apiserver
+func newCertManager(
+	clientset kubernetes.Interface,
+	componentName,
+	certDir,
+	commonName string,
+	organizations []string,
+	ipAddrs []net.IP) (certificate.Manager, error) {
+	certificateStore, err :=
+		certificate.NewFileStore(componentName, certDir, certDir, "", "")
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize the server certificate store: %v", err)
+	}
+
+	getTemplate := func() *x509.CertificateRequest {
+		return &x509.CertificateRequest{
+			Subject: pkix.Name{
+				CommonName:   commonName,
+				Organization: organizations,
+			},
+			IPAddresses: ipAddrs,
+		}
+	}
+
+	certManager, err := certificate.NewManager(&certificate.Config{
+		ClientFn: func(current *tls.Certificate) (clicert.CertificateSigningRequestInterface, error) {
+			return clientset.CertificatesV1beta1().CertificateSigningRequests(), nil
+		},
+		SignerName:  certificates.LegacyUnknownSignerName,
+		GetTemplate: getTemplate,
+		Usages: []certificates.KeyUsage{
+			certificates.UsageKeyEncipherment,
+			certificates.UsageDigitalSignature,
+			certificates.UsageServerAuth,
+		},
+		CertificateStore: certificateStore,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to initialize server certificate manager: %v", err)
+	}
+
+	return certManager, nil
+}
diff --git a/pkg/yurthub/server/server.go b/pkg/yurthub/server/server.go
index 8bd59626..33d1b673 100644
--- a/pkg/yurthub/server/server.go
+++ b/pkg/yurthub/server/server.go
@@ -21,13 +21,21 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"time"
 
 	"github.com/gorilla/mux"
 	"github.com/prometheus/client_golang/prometheus/promhttp"
+	"k8s.io/apimachinery/pkg/util/wait"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/klog/v2"
 
 	"github.com/openyurtio/openyurt/cmd/yurthub/app/config"
 	"github.com/openyurtio/openyurt/pkg/profile"
+	"github.com/openyurtio/openyurt/pkg/projectinfo"
+	"github.com/openyurtio/openyurt/pkg/yurthub/certificate"
 	"github.com/openyurtio/openyurt/pkg/yurthub/certificate/interfaces"
+	"github.com/openyurtio/openyurt/pkg/yurthub/certificate/server"
+	"github.com/openyurtio/openyurt/pkg/yurthub/kubernetes/rest"
 )
 
 // Server is an interface for providing http service for yurthub
@@ -160,3 +168,46 @@ func healthz(w http.ResponseWriter, _ *http.Request) {
 	w.WriteHeader(http.StatusOK)
 	fmt.Fprintf(w, "OK")
 }
+
+// create a certificate manager for the yurthub server and run the csr approver for both yurthub
+// and generate a TLS configuration
+func GenUseCertMgrAndTLSConfig(restConfigMgr *rest.RestConfigManager, certificateMgr interfaces.YurtCertificateManager, certDir, proxyServerSecureDummyAddr string, 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")
+	}
+
+	clientSet, err := kubernetes.NewForConfig(cfg)
+	if err != nil {
+		return nil, err
+	}
+	// create a certificate manager for the yurthub server and run the csr approver for both yurthub
+	serverCertMgr, err := server.NewYurtHubServerCertManager(clientSet, certDir, proxyServerSecureDummyAddr)
+	if err != nil {
+		return nil, err
+	}
+	serverCertMgr.Start()
+
+	// generate the TLS configuration based on the latest certificate
+	rootCert, err := certificate.GenCertPoolUseCA(certificateMgr.GetCaFile())
+	if err != nil {
+		klog.Errorf("could not generate a x509 CertPool based on the given CA file, %v", err)
+		return nil, err
+	}
+	tlsCfg, err := certificate.GenTLSConfigUseCertMgrAndCertPool(serverCertMgr, rootCert)
+	if err != nil {
+		return nil, err
+	}
+
+	// waiting for the certificate is generated
+	_ = wait.PollUntil(5*time.Second, func() (bool, error) {
+		// keep polling until the certificate is signed
+		if serverCertMgr.Current() != nil {
+			return true, nil
+		}
+		klog.Infof("waiting for the master to sign the %s certificate", projectinfo.GetHubName())
+		return false, nil
+	}, stopCh)
+
+	return tlsCfg, nil
+}


Test patch:
diff --git a/pkg/controller/kubernetes/controller/testutil/test_utils.go b/pkg/controller/kubernetes/controller/testutil/test_utils.go
new file mode 100644
index 00000000..3e047828
--- /dev/null
+++ b/pkg/controller/kubernetes/controller/testutil/test_utils.go
@@ -0,0 +1,521 @@
+/*
+Copyright 2016 The Kubernetes Authors.
+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 testutil
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"reflect"
+	"sync"
+	"testing"
+	"time"
+
+	jsonpatch "github.com/evanphx/json-patch"
+	v1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/clock"
+	"k8s.io/apimachinery/pkg/util/sets"
+	"k8s.io/apimachinery/pkg/util/strategicpatch"
+	"k8s.io/apimachinery/pkg/watch"
+	"k8s.io/client-go/kubernetes/fake"
+	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
+	"k8s.io/client-go/tools/cache"
+	ref "k8s.io/client-go/tools/reference"
+	"k8s.io/klog/v2"
+
+	utilnode "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/node"
+)
+
+var (
+	keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc
+)
+
+// FakeNodeHandler is a fake implementation of NodesInterface and NodeInterface. It
+// allows test cases to have fine-grained control over mock behaviors. We also need
+// PodsInterface and PodInterface to test list & delete pods, which is implemented in
+// the embedded client.Fake field.
+type FakeNodeHandler struct {
+	*fake.Clientset
+
+	// Input: Hooks determine if request is valid or not
+	CreateHook func(*FakeNodeHandler, *v1.Node) bool
+	Existing   []*v1.Node
+
+	// Output
+	CreatedNodes        []*v1.Node
+	DeletedNodes        []*v1.Node
+	UpdatedNodes        []*v1.Node
+	UpdatedNodeStatuses []*v1.Node
+	RequestCount        int
+
+	// Synchronization
+	lock           sync.Mutex
+	DeleteWaitChan chan struct{}
+	PatchWaitChan  chan struct{}
+}
+
+// FakeLegacyHandler is a fake implementation of CoreV1Interface.
+type FakeLegacyHandler struct {
+	v1core.CoreV1Interface
+	n *FakeNodeHandler
+}
+
+// GetUpdatedNodesCopy returns a slice of Nodes with updates applied.
+func (m *FakeNodeHandler) GetUpdatedNodesCopy() []*v1.Node {
+	m.lock.Lock()
+	defer m.lock.Unlock()
+	updatedNodesCopy := make([]*v1.Node, len(m.UpdatedNodes), len(m.UpdatedNodes))
+	for i, ptr := range m.UpdatedNodes {
+		updatedNodesCopy[i] = ptr
+	}
+	return updatedNodesCopy
+}
+
+// Core returns fake CoreInterface.
+func (m *FakeNodeHandler) Core() v1core.CoreV1Interface {
+	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
+}
+
+// CoreV1 returns fake CoreV1Interface
+func (m *FakeNodeHandler) CoreV1() v1core.CoreV1Interface {
+	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
+}
+
+// Nodes return fake NodeInterfaces.
+func (m *FakeLegacyHandler) Nodes() v1core.NodeInterface {
+	return m.n
+}
+
+// Create adds a new Node to the fake store.
+func (m *FakeNodeHandler) Create(_ context.Context, node *v1.Node, _ metav1.CreateOptions) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+	for _, n := range m.Existing {
+		if n.Name == node.Name {
+			return nil, apierrors.NewAlreadyExists(Resource("nodes"), node.Name)
+		}
+	}
+	if m.CreateHook == nil || m.CreateHook(m, node) {
+		nodeCopy := *node
+		m.CreatedNodes = append(m.CreatedNodes, &nodeCopy)
+		return node, nil
+	}
+	return nil, errors.New("create error")
+}
+
+// Get returns a Node from the fake store.
+func (m *FakeNodeHandler) Get(_ context.Context, name string, opts metav1.GetOptions) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+	for i := range m.UpdatedNodes {
+		if m.UpdatedNodes[i].Name == name {
+			nodeCopy := *m.UpdatedNodes[i]
+			return &nodeCopy, nil
+		}
+	}
+	for i := range m.Existing {
+		if m.Existing[i].Name == name {
+			nodeCopy := *m.Existing[i]
+			return &nodeCopy, nil
+		}
+	}
+	return nil, nil
+}
+
+// List returns a list of Nodes from the fake store.
+func (m *FakeNodeHandler) List(_ context.Context, opts metav1.ListOptions) (*v1.NodeList, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+	var nodes []*v1.Node
+	for i := 0; i < len(m.UpdatedNodes); i++ {
+		if !contains(m.UpdatedNodes[i], m.DeletedNodes) {
+			nodes = append(nodes, m.UpdatedNodes[i])
+		}
+	}
+	for i := 0; i < len(m.Existing); i++ {
+		if !contains(m.Existing[i], m.DeletedNodes) && !contains(m.Existing[i], nodes) {
+			nodes = append(nodes, m.Existing[i])
+		}
+	}
+	for i := 0; i < len(m.CreatedNodes); i++ {
+		if !contains(m.CreatedNodes[i], m.DeletedNodes) && !contains(m.CreatedNodes[i], nodes) {
+			nodes = append(nodes, m.CreatedNodes[i])
+		}
+	}
+	nodeList := &v1.NodeList{}
+	for _, node := range nodes {
+		nodeList.Items = append(nodeList.Items, *node)
+	}
+	return nodeList, nil
+}
+
+// Delete deletes a Node from the fake store.
+func (m *FakeNodeHandler) Delete(_ context.Context, id string, opt metav1.DeleteOptions) error {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		if m.DeleteWaitChan != nil {
+			m.DeleteWaitChan <- struct{}{}
+		}
+		m.lock.Unlock()
+	}()
+	m.DeletedNodes = append(m.DeletedNodes, NewNode(id))
+	return nil
+}
+
+// DeleteCollection deletes a collection of Nodes from the fake store.
+func (m *FakeNodeHandler) DeleteCollection(_ context.Context, opt metav1.DeleteOptions, listOpts metav1.ListOptions) error {
+	return nil
+}
+
+// Update updates a Node in the fake store.
+func (m *FakeNodeHandler) Update(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+
+	nodeCopy := *node
+	for i, updateNode := range m.UpdatedNodes {
+		if updateNode.Name == nodeCopy.Name {
+			m.UpdatedNodes[i] = &nodeCopy
+			return node, nil
+		}
+	}
+	m.UpdatedNodes = append(m.UpdatedNodes, &nodeCopy)
+	return node, nil
+}
+
+// UpdateStatus updates a status of a Node in the fake store.
+func (m *FakeNodeHandler) UpdateStatus(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		m.lock.Unlock()
+	}()
+
+	var origNodeCopy v1.Node
+	found := false
+	for i := range m.Existing {
+		if m.Existing[i].Name == node.Name {
+			origNodeCopy = *m.Existing[i]
+			found = true
+			break
+		}
+	}
+	updatedNodeIndex := -1
+	for i := range m.UpdatedNodes {
+		if m.UpdatedNodes[i].Name == node.Name {
+			origNodeCopy = *m.UpdatedNodes[i]
+			updatedNodeIndex = i
+			found = true
+			break
+		}
+	}
+
+	if !found {
+		return nil, fmt.Errorf("not found node %v", node)
+	}
+
+	origNodeCopy.Status = node.Status
+	if updatedNodeIndex < 0 {
+		m.UpdatedNodes = append(m.UpdatedNodes, &origNodeCopy)
+	} else {
+		m.UpdatedNodes[updatedNodeIndex] = &origNodeCopy
+	}
+
+	nodeCopy := *node
+	m.UpdatedNodeStatuses = append(m.UpdatedNodeStatuses, &nodeCopy)
+	return node, nil
+}
+
+// PatchStatus patches a status of a Node in the fake store.
+func (m *FakeNodeHandler) PatchStatus(ctx context.Context, nodeName string, data []byte) (*v1.Node, error) {
+	m.RequestCount++
+	return m.Patch(ctx, nodeName, types.StrategicMergePatchType, data, metav1.PatchOptions{}, "status")
+}
+
+// Watch watches Nodes in a fake store.
+func (m *FakeNodeHandler) Watch(_ context.Context, opts metav1.ListOptions) (watch.Interface, error) {
+	return watch.NewFake(), nil
+}
+
+// Patch patches a Node in the fake store.
+func (m *FakeNodeHandler) Patch(_ context.Context, name string, pt types.PatchType, data []byte, _ metav1.PatchOptions, subresources ...string) (*v1.Node, error) {
+	m.lock.Lock()
+	defer func() {
+		m.RequestCount++
+		if m.PatchWaitChan != nil {
+			m.PatchWaitChan <- struct{}{}
+		}
+		m.lock.Unlock()
+	}()
+	var nodeCopy v1.Node
+	for i := range m.Existing {
+		if m.Existing[i].Name == name {
+			nodeCopy = *m.Existing[i]
+		}
+	}
+	updatedNodeIndex := -1
+	for i := range m.UpdatedNodes {
+		if m.UpdatedNodes[i].Name == name {
+			nodeCopy = *m.UpdatedNodes[i]
+			updatedNodeIndex = i
+		}
+	}
+
+	originalObjJS, err := json.Marshal(nodeCopy)
+	if err != nil {
+		klog.Errorf("Failed to marshal %v", nodeCopy)
+		return nil, nil
+	}
+	var originalNode v1.Node
+	if err = json.Unmarshal(originalObjJS, &originalNode); err != nil {
+		klog.Errorf("Failed to unmarshal original object: %v", err)
+		return nil, nil
+	}
+
+	var patchedObjJS []byte
+	switch pt {
+	case types.JSONPatchType:
+		patchObj, err := jsonpatch.DecodePatch(data)
+		if err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+		if patchedObjJS, err = patchObj.Apply(originalObjJS); err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+	case types.MergePatchType:
+		if patchedObjJS, err = jsonpatch.MergePatch(originalObjJS, data); err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+	case types.StrategicMergePatchType:
+		if patchedObjJS, err = strategicpatch.StrategicMergePatch(originalObjJS, data, originalNode); err != nil {
+			klog.Error(err.Error())
+			return nil, nil
+		}
+	default:
+		klog.Errorf("unknown Content-Type header for patch: %v", pt)
+		return nil, nil
+	}
+
+	var updatedNode v1.Node
+	if err = json.Unmarshal(patchedObjJS, &updatedNode); err != nil {
+		klog.Errorf("Failed to unmarshal patched object: %v", err)
+		return nil, nil
+	}
+
+	if updatedNodeIndex < 0 {
+		m.UpdatedNodes = append(m.UpdatedNodes, &updatedNode)
+	} else {
+		m.UpdatedNodes[updatedNodeIndex] = &updatedNode
+	}
+
+	return &updatedNode, nil
+}
+
+// FakeRecorder is used as a fake during testing.
+type FakeRecorder struct {
+	sync.Mutex
+	source v1.EventSource
+	Events []*v1.Event
+	clock  clock.Clock
+}
+
+// Event emits a fake event to the fake recorder
+func (f *FakeRecorder) Event(obj runtime.Object, eventtype, reason, message string) {
+	f.generateEvent(obj, metav1.Now(), eventtype, reason, message)
+}
+
+// Eventf emits a fake formatted event to the fake recorder
+func (f *FakeRecorder) Eventf(obj runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
+	f.Event(obj, eventtype, reason, fmt.Sprintf(messageFmt, args...))
+}
+
+// AnnotatedEventf emits a fake formatted event to the fake recorder
+func (f *FakeRecorder) AnnotatedEventf(obj runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
+	f.Eventf(obj, eventtype, reason, messageFmt, args...)
+}
+
+func (f *FakeRecorder) generateEvent(obj runtime.Object, timestamp metav1.Time, eventtype, reason, message string) {
+	f.Lock()
+	defer f.Unlock()
+	ref, err := ref.GetReference(runtime.NewScheme(), obj)
+	if err != nil {
+		klog.Errorf("Encountered error while getting reference: %v", err)
+		return
+	}
+	event := f.makeEvent(ref, eventtype, reason, message)
+	event.Source = f.source
+	if f.Events != nil {
+		f.Events = append(f.Events, event)
+	}
+}
+
+func (f *FakeRecorder) makeEvent(ref *v1.ObjectReference, eventtype, reason, message string) *v1.Event {
+	t := metav1.Time{Time: f.clock.Now()}
+	namespace := ref.Namespace
+	if namespace == "" {
+		namespace = metav1.NamespaceDefault
+	}
+
+	clientref := v1.ObjectReference{
+		Kind:            ref.Kind,
+		Namespace:       ref.Namespace,
+		Name:            ref.Name,
+		UID:             ref.UID,
+		APIVersion:      ref.APIVersion,
+		ResourceVersion: ref.ResourceVersion,
+		FieldPath:       ref.FieldPath,
+	}
+
+	return &v1.Event{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
+			Namespace: namespace,
+		},
+		InvolvedObject: clientref,
+		Reason:         reason,
+		Message:        message,
+		FirstTimestamp: t,
+		LastTimestamp:  t,
+		Count:          1,
+		Type:           eventtype,
+	}
+}
+
+// NewFakeRecorder returns a pointer to a newly constructed FakeRecorder.
+func NewFakeRecorder() *FakeRecorder {
+	return &FakeRecorder{
+		source: v1.EventSource{Component: "nodeControllerTest"},
+		Events: []*v1.Event{},
+		clock:  clock.NewFakeClock(time.Now()),
+	}
+}
+
+// NewNode is a helper function for creating Nodes for testing.
+func NewNode(name string) *v1.Node {
+	return &v1.Node{
+		ObjectMeta: metav1.ObjectMeta{Name: name},
+		Status: v1.NodeStatus{
+			Capacity: v1.ResourceList{
+				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
+				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
+			},
+		},
+	}
+}
+
+// NewPod is a helper function for creating Pods for testing.
+func NewPod(name, host string) *v1.Pod {
+	pod := &v1.Pod{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: "default",
+			Name:      name,
+		},
+		Spec: v1.PodSpec{
+			NodeName: host,
+		},
+		Status: v1.PodStatus{
+			Conditions: []v1.PodCondition{
+				{
+					Type:   v1.PodReady,
+					Status: v1.ConditionTrue,
+				},
+			},
+		},
+	}
+
+	return pod
+}
+
+func contains(node *v1.Node, nodes []*v1.Node) bool {
+	for i := 0; i < len(nodes); i++ {
+		if node.Name == nodes[i].Name {
+			return true
+		}
+	}
+	return false
+}
+
+// GetZones returns list of zones for all Nodes stored in FakeNodeHandler
+func GetZones(nodeHandler *FakeNodeHandler) []string {
+	nodes, _ := nodeHandler.List(context.TODO(), metav1.ListOptions{})
+	zones := sets.NewString()
+	for _, node := range nodes.Items {
+		zones.Insert(utilnode.GetZoneKey(&node))
+	}
+	return zones.List()
+}
+
+// CreateZoneID returns a single zoneID for a given region and zone.
+func CreateZoneID(region, zone string) string {
+	return region + ":\x00:" + zone
+}
+
+// GetKey is a helper function used by controllers unit tests to get the
+// key for a given kubernetes resource.
+func GetKey(obj interface{}, t *testing.T) string {
+	tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
+	if ok {
+		// if tombstone , try getting the value from tombstone.Obj
+		obj = tombstone.Obj
+	}
+	val := reflect.ValueOf(obj).Elem()
+	name := val.FieldByName("Name").String()
+	kind := val.FieldByName("Kind").String()
+	// Note kind is not always set in the tests, so ignoring that for now
+	if len(name) == 0 || len(kind) == 0 {
+		t.Errorf("Unexpected object %v", obj)
+	}
+
+	key, err := keyFunc(obj)
+	if err != nil {
+		t.Errorf("Unexpected error getting key for %v %v: %v", kind, name, err)
+		return ""
+	}
+	return key
+}
+
+// SchemeGroupVersion is group version used to register these objects
+var SchemeGroupVersion = schema.GroupVersion{Group: "", Version: runtime.APIVersionInternal}
+
+// Resource takes an unqualified resource and returns a Group qualified GroupResource
+func Resource(resource string) schema.GroupResource {
+	return SchemeGroupVersion.WithResource(resource).GroupResource()
+}
diff --git a/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go b/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go
index 27e30f62..bb1e13e2 100644
--- a/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go
+++ b/pkg/controller/nodelifecycle/node_lifecycle_controller_test.go
@@ -43,15 +43,14 @@ import (
 	"k8s.io/client-go/kubernetes/fake"
 	testcore "k8s.io/client-go/testing"
 	featuregatetesting "k8s.io/component-base/featuregate/testing"
-	"k8s.io/kubernetes/pkg/controller"
-	"k8s.io/kubernetes/pkg/controller/testutil"
-	nodeutil "k8s.io/kubernetes/pkg/controller/util/node"
-	"k8s.io/kubernetes/pkg/features"
-	kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
-	"k8s.io/kubernetes/pkg/util/node"
-	taintutils "k8s.io/kubernetes/pkg/util/taints"
 	"k8s.io/utils/pointer"
 
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller"
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller/testutil"
+	nodeutil "github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller/util/node"
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/features"
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/node"
+	taintutils "github.com/openyurtio/openyurt/pkg/controller/kubernetes/util/taints"
 	"github.com/openyurtio/openyurt/pkg/controller/nodelifecycle/scheduler"
 )
 
@@ -3296,16 +3295,16 @@ func TestReconcileNodeLabels(t *testing.T) {
 					Name:              "node0",
 					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
 					Labels: map[string]string{
-						kubeletapis.LabelOS:   "linux",
-						kubeletapis.LabelArch: "amd64",
+						LabelOS:   "linux",
+						LabelArch: "amd64",
 					},
 				},
 			},
 			ExpectedLabels: map[string]string{
-				kubeletapis.LabelOS:   "linux",
-				kubeletapis.LabelArch: "amd64",
-				v1.LabelOSStable:      "linux",
-				v1.LabelArchStable:    "amd64",
+				LabelOS:            "linux",
+				LabelArch:          "amd64",
+				v1.LabelOSStable:   "linux",
+				v1.LabelArchStable: "amd64",
 			},
 		},
 		{
@@ -3315,18 +3314,18 @@ func TestReconcileNodeLabels(t *testing.T) {
 					Name:              "node0",
 					CreationTimestamp: metav1.Date(2012, 1, 1, 0, 0, 0, 0, time.UTC),
 					Labels: map[string]string{
-						kubeletapis.LabelOS:   "linux",
-						kubeletapis.LabelArch: "amd64",
-						v1.LabelOSStable:      "windows",
-						v1.LabelArchStable:    "arm",
+						LabelOS:            "linux",
+						LabelArch:          "amd64",
+						v1.LabelOSStable:   "windows",
+						v1.LabelArchStable: "arm",
 					},
 				},
 			},
 			ExpectedLabels: map[string]string{
-				kubeletapis.LabelOS:   "linux",
-				kubeletapis.LabelArch: "amd64",
-				v1.LabelOSStable:      "linux",
-				v1.LabelArchStable:    "amd64",
+				LabelOS:            "linux",
+				LabelArch:          "amd64",
+				v1.LabelOSStable:   "linux",
+				v1.LabelArchStable: "amd64",
 			},
 		},
 	}
diff --git a/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go b/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go
index 5a4c30ca..7948a181 100644
--- a/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go
+++ b/pkg/controller/nodelifecycle/scheduler/taint_manager_test.go
@@ -30,7 +30,8 @@ import (
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/kubernetes/fake"
 	clienttesting "k8s.io/client-go/testing"
-	"k8s.io/kubernetes/pkg/controller/testutil"
+
+	"github.com/openyurtio/openyurt/pkg/controller/kubernetes/controller/testutil"
 )
 
 var timeForControllerToProgress = 500 * time.Millisecond