Repository: kubernetes-sigs/cloud-provider-azure
Pull request #614 (2021-05-05 01:34:50)

docs: add docs for ccm dynamic reloading
<!--  Thanks for sending a pull request!  Here are some tips for you:

1. If this is your first time, please read our contributor guidelines: https://git.k8s.io/community/contributors/guide#your-first-contribution and developer guide https://git.k8s.io/community/contributors/devel/development.md#development-guide
2. Please label this pull request according to what type of issue you are addressing, especially if this is a release targeted pull request. For reference on required PR/issue labels, read here:
https://git.k8s.io/community/contributors/devel/release.md#issue-kind-label
3. Ensure you have added or ran the appropriate tests for your PR: https://git.k8s.io/community/contributors/devel/testing.md
4. If you want *faster* PR reviews, read how: https://git.k8s.io/community/contributors/guide/pull-requests.md#best-practices-for-faster-reviews
5. Follow the instructions for writing a release note: https://git.k8s.io/community/contributors/guide/release-notes.md
6. If the PR is unfinished, see how to mark it: https://git.k8s.io/community/contributors/guide/pull-requests.md#marking-unfinished-pull-requests
-->

**What type of PR is this?**
> Uncomment only one ` /kind <>` line, hit enter to put that in a new line, and remove leading whitespaces from that line:
>
> /kind api-change
> /kind bug
> /kind cleanup
> /kind design
> /kind documentation
> /kind failing-test
> /kind feature
> /kind flake

 /kind documentation

**What this PR does / why we need it**:

docs: add docs for ccm dynamic reloading

**Which issue(s) this PR fixes**:
<!-- 
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #

**Special notes for your reviewer**:


**Release note**:
```
docs: add docs for ccm dynamic reloading
```



Patch:
diff --git a/cmd/cloud-controller-manager/app/config/config.go b/cmd/cloud-controller-manager/app/config/config.go
index 5bb0d5629..07ea3967e 100644
--- a/cmd/cloud-controller-manager/app/config/config.go
+++ b/cmd/cloud-controller-manager/app/config/config.go
@@ -60,15 +60,6 @@ type Config struct {
 
 	// SharedInformers gives access to informers for the controller.
 	SharedInformers informers.SharedInformerFactory
-
-	DynamicReloadingConfig DynamicReloadingConfig
-}
-
-type DynamicReloadingConfig struct {
-	EnableDynamicReloading     bool
-	CloudConfigSecretName      string
-	CloudConfigSecretNamespace string
-	CloudConfigKey             string
 }
 
 type completedConfig struct {
diff --git a/cmd/cloud-controller-manager/app/controllermanager.go b/cmd/cloud-controller-manager/app/controllermanager.go
index 002943748..0dfdba78c 100644
--- a/cmd/cloud-controller-manager/app/controllermanager.go
+++ b/cmd/cloud-controller-manager/app/controllermanager.go
@@ -18,6 +18,7 @@ package app
 
 import (
 	"context"
+	"flag"
 	"fmt"
 	"net/http"
 	"os"
@@ -41,9 +42,7 @@ import (
 	"k8s.io/klog/v2"
 
 	cloudcontrollerconfig "sigs.k8s.io/cloud-provider-azure/cmd/cloud-controller-manager/app/config"
-	"sigs.k8s.io/cloud-provider-azure/cmd/cloud-controller-manager/app/dynamic"
 	"sigs.k8s.io/cloud-provider-azure/cmd/cloud-controller-manager/app/options"
-	"sigs.k8s.io/cloud-provider-azure/pkg/provider"
 	"sigs.k8s.io/cloud-provider-azure/pkg/version"
 	"sigs.k8s.io/cloud-provider-azure/pkg/version/verflag"
 )
@@ -71,66 +70,15 @@ func NewCloudControllerManagerCommand() *cobra.Command {
 
 			c, err := s.Config(KnownControllers(), ControllersDisabledByDefault.List())
 			if err != nil {
-				klog.Errorf("Run: failed to configure cloud controller manager: %v", err)
+				fmt.Fprintf(os.Stderr, "%v\n", err)
 				os.Exit(1)
 			}
 
-			err = StartHTTPServer(c.Complete(), wait.NeverStop)
-			if err != nil {
-				klog.Errorf("Run: railed to start HTTP server: %v", err)
+			if err := Run(c.Complete(), wait.NeverStop); err != nil {
+				fmt.Fprintf(os.Stderr, "%v\n", err)
 				os.Exit(1)
 			}
 
-			if c.ComponentConfig.Generic.LeaderElection.LeaderElect {
-				// Identity used to distinguish between multiple cloud controller manager instances
-				id, err := os.Hostname()
-				if err != nil {
-					klog.Errorf("Run: failed to get host name: %v", err)
-					os.Exit(1)
-				}
-				// add a uniquifier so that two processes on the same host don't accidentally both become active
-				id = id + "_" + string(uuid.NewUUID())
-
-				// Lock required for leader election
-				rl, err := resourcelock.NewFromKubeconfig(c.ComponentConfig.Generic.LeaderElection.ResourceLock,
-					c.ComponentConfig.Generic.LeaderElection.ResourceNamespace,
-					c.ComponentConfig.Generic.LeaderElection.ResourceName,
-					resourcelock.ResourceLockConfig{
-						Identity:      id,
-						EventRecorder: c.EventRecorder,
-					},
-					c.Kubeconfig,
-					c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration)
-				if err != nil {
-					klog.Fatalf("error creating lock: %v", err)
-				}
-
-				// Try and become the leader and start cloud controller manager loops
-				var electionChecker *leaderelection.HealthzAdaptor
-				if c.ComponentConfig.Generic.LeaderElection.LeaderElect {
-					electionChecker = leaderelection.NewLeaderHealthzAdaptor(time.Second * 20)
-				}
-
-				leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{
-					Lock:          rl,
-					LeaseDuration: c.ComponentConfig.Generic.LeaderElection.LeaseDuration.Duration,
-					RenewDeadline: c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration,
-					RetryPeriod:   c.ComponentConfig.Generic.LeaderElection.RetryPeriod.Duration,
-					Callbacks: leaderelection.LeaderCallbacks{
-						OnStartedLeading: RunWrapper(s, c),
-						OnStoppedLeading: func() {
-							panic("leaderelection lost")
-						},
-					},
-					WatchDog: electionChecker,
-					Name:     "cloud-controller-manager",
-				})
-
-				panic("unreachable")
-			}
-
-			RunWrapper(s, c)(context.TODO())
-			panic("unreachable")
 		},
 	}
 
@@ -139,6 +87,11 @@ func NewCloudControllerManagerCommand() *cobra.Command {
 	verflag.AddFlags(namedFlagSets.FlagSet("global"))
 	globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name())
 
+	if flag.CommandLine.Lookup("cloud-provider-gce-lb-src-cidrs") != nil {
+		// hoist this flag from the global flagset to preserve the commandline until
+		// the gce cloudprovider is removed.
+		globalflag.Register(namedFlagSets.FlagSet("generic"), "cloud-provider-gce-lb-src-cidrs")
+	}
 	for _, f := range namedFlagSets.FlagSets {
 		fs.AddFlagSet(f)
 	}
@@ -157,66 +110,34 @@ func NewCloudControllerManagerCommand() *cobra.Command {
 	return cmd
 }
 
-// RunWrapper adapts the ccm boot logic to the leader elector call back function
-func RunWrapper(s *options.CloudControllerManagerOptions, c *cloudcontrollerconfig.Config) func(ctx context.Context) {
-	return func(ctx context.Context) {
-		if !c.DynamicReloadingConfig.EnableDynamicReloading {
-			klog.V(1).Infof("using static initialization from config file %s", c.ComponentConfig.KubeCloudShared.CloudProvider.CloudConfigFile)
-			if err := Run(context.TODO(), c.Complete()); err != nil {
-				klog.Errorf("RunWrapper: failed to start cloud controller manager: %v", err)
-				os.Exit(1)
-			}
+// Run runs the ExternalCMServer.  This should never exit.
+func Run(c *cloudcontrollerconfig.CompletedConfig, stopCh <-chan struct{}) error {
+	// To help debugging, immediately log version
+	klog.Infof("Version: %+v", version.Get())
 
-			panic("unreachable")
-		} else {
-			klog.V(1).Infof("RunWrapper: using dynamic initialization from secret %s/%s, starting the secret watcher", c.DynamicReloadingConfig.CloudConfigSecretNamespace, c.DynamicReloadingConfig.CloudConfigSecretName)
-			updateCh := dynamic.RunSecretWatcherOrDie(c)
-			errCh := make(chan error, 1)
-
-			cancelFunc := runAsync(s, errCh)
-
-			for {
-				select {
-				case <-updateCh:
-					klog.V(2).Infof("RunWrapper: detected the cloud config secret %s/%s has been updated, re-constructing the cloud controller manager", c.DynamicReloadingConfig.CloudConfigSecretNamespace, c.DynamicReloadingConfig.CloudConfigSecretName)
-
-					// stop the previous goroutines
-					cancelFunc()
-
-					// start new goroutines
-					cancelFunc = runAsync(s, errCh)
-				case err := <-errCh:
-					klog.Errorf("RunWrapper: failed to start cloud controller manager: %v", err)
-					os.Exit(1)
-				}
-			}
-		}
+	cloud, err := cloudprovider.InitCloudProvider(c.ComponentConfig.KubeCloudShared.CloudProvider.Name, c.ComponentConfig.KubeCloudShared.CloudProvider.CloudConfigFile)
+	if err != nil {
+		klog.Fatalf("Cloud provider could not be initialized: %v", err)
+	}
+	if cloud == nil {
+		klog.Fatalf("cloud provider is nil")
 	}
-}
-
-func runAsync(s *options.CloudControllerManagerOptions, errCh chan error) context.CancelFunc {
-	ctx, cancelFunc := context.WithCancel(context.Background())
-
-	go func() {
-		c, err := s.Config(KnownControllers(), ControllersDisabledByDefault.List())
-		if err != nil {
-			klog.Errorf("RunAsync: failed to configure cloud controller manager: %v", err)
-			os.Exit(1)
-		}
 
-		if err := Run(ctx, c.Complete()); err != nil {
-			klog.Errorf("RunAsync: failed to run cloud controller manager: %v", err)
-			errCh <- err
+	if !cloud.HasClusterID() {
+		if c.ComponentConfig.KubeCloudShared.AllowUntaggedCloud {
+			klog.Warning("detected a cluster without a ClusterID.  A ClusterID will be required in the future.  Please tag your cluster to avoid any future issues")
+		} else {
+			klog.Fatalf("no ClusterID found.  A ClusterID is required for the cloud provider to function properly.  This check can be bypassed by setting the allow-untagged-cloud option")
 		}
+	}
 
-		klog.V(1).Infof("RunAsync: stopping")
-	}()
-
-	return cancelFunc
-}
+	// setup /configz endpoint
+	if cz, err := configz.New(ConfigzName); err == nil {
+		cz.Set(c.ComponentConfig)
+	} else {
+		klog.Errorf("unable to register configz: %v", err)
+	}
 
-// StartHTTPServer starts the controller manager HTTP server
-func StartHTTPServer(c *cloudcontrollerconfig.CompletedConfig, stopCh <-chan struct{}) error {
 	// Setup any healthz checks we will want to use.
 	var checks []healthz.HealthChecker
 	var electionChecker *leaderelection.HealthzAdaptor
@@ -247,55 +168,55 @@ func StartHTTPServer(c *cloudcontrollerconfig.CompletedConfig, stopCh <-chan str
 		healthz.InstallReadyzHandler(unsecuredMux, checks...)
 	}
 
-	return nil
-}
-
-// Run runs the ExternalCMServer.  This should never exit.
-func Run(ctx context.Context, c *cloudcontrollerconfig.CompletedConfig) error {
-	// To help debugging, immediately log version
-	klog.Infof("Version: %+v", version.Get())
-
-	var (
-		cloud cloudprovider.Interface
-		err   error
-	)
-
-	if c.DynamicReloadingConfig.EnableDynamicReloading {
-		cloud, err = provider.NewCloudFromSecret(c.ClientBuilder, c.DynamicReloadingConfig.CloudConfigSecretName, c.DynamicReloadingConfig.CloudConfigSecretNamespace, c.DynamicReloadingConfig.CloudConfigKey)
-		if err != nil {
-			klog.Fatalf("Run: Cloud provider could not be initialized dynamically from secret %s/%s: %v", c.DynamicReloadingConfig.CloudConfigSecretNamespace, c.DynamicReloadingConfig.CloudConfigSecretName, err)
+	run := func(ctx context.Context) {
+		if err := startControllers(c, ctx.Done(), cloud, newControllerInitializers()); err != nil {
+			klog.Fatalf("error running controllers: %v", err)
 		}
-	} else {
-		cloud, err = cloudprovider.InitCloudProvider(c.ComponentConfig.KubeCloudShared.CloudProvider.Name, c.ComponentConfig.KubeCloudShared.CloudProvider.CloudConfigFile)
-		if err != nil {
-			klog.Fatalf("Cloud provider could not be initialized: %v", err)
-		}
-	}
-
-	if cloud == nil {
-		klog.Fatalf("cloud provider is nil")
 	}
 
-	if !cloud.HasClusterID() {
-		if c.ComponentConfig.KubeCloudShared.AllowUntaggedCloud {
-			klog.Warning("detected a cluster without a ClusterID.  A ClusterID will be required in the future.  Please tag your cluster to avoid any future issues")
-		} else {
-			klog.Fatalf("no ClusterID found.  A ClusterID is required for the cloud provider to function properly.  This check can be bypassed by setting the allow-untagged-cloud option")
-		}
+	if !c.ComponentConfig.Generic.LeaderElection.LeaderElect {
+		run(context.TODO())
+		panic("unreachable")
 	}
 
-	// setup /configz endpoint
-	if cz, err := configz.New(ConfigzName); err == nil {
-		cz.Set(c.ComponentConfig)
-	} else {
-		klog.Errorf("unable to register configz: %v", err)
+	// Identity used to distinguish between multiple cloud controller manager instances
+	id, err := os.Hostname()
+	if err != nil {
+		return err
 	}
-
-	if err := startControllers(c, ctx.Done(), cloud, newControllerInitializers()); err != nil {
-		klog.Fatalf("error running controllers: %v", err)
+	// add a uniquifier so that two processes on the same host don't accidentally both become active
+	id = id + "_" + string(uuid.NewUUID())
+
+	// Lock required for leader election
+	rl, err := resourcelock.NewFromKubeconfig(c.ComponentConfig.Generic.LeaderElection.ResourceLock,
+		c.ComponentConfig.Generic.LeaderElection.ResourceNamespace,
+		c.ComponentConfig.Generic.LeaderElection.ResourceName,
+		resourcelock.ResourceLockConfig{
+			Identity:      id,
+			EventRecorder: c.EventRecorder,
+		},
+		c.Kubeconfig,
+		c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration)
+	if err != nil {
+		klog.Fatalf("error creating lock: %v", err)
 	}
 
-	return nil
+	// Try and become the leader and start cloud controller manager loops
+	leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{
+		Lock:          rl,
+		LeaseDuration: c.ComponentConfig.Generic.LeaderElection.LeaseDuration.Duration,
+		RenewDeadline: c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration,
+		RetryPeriod:   c.ComponentConfig.Generic.LeaderElection.RetryPeriod.Duration,
+		Callbacks: leaderelection.LeaderCallbacks{
+			OnStartedLeading: run,
+			OnStoppedLeading: func() {
+				klog.Fatalf("leaderelection lost")
+			},
+		},
+		WatchDog: electionChecker,
+		Name:     "cloud-controller-manager",
+	})
+	panic("unreachable")
 }
 
 // startControllers starts the cloud specific controller loops.
@@ -334,13 +255,9 @@ func startControllers(c *cloudcontrollerconfig.CompletedConfig, stopCh <-chan st
 		klog.Fatalf("Failed to wait for apiserver being healthy: %v", err)
 	}
 
-	klog.V(2).Infof("startControllers: starting shared informers")
 	c.SharedInformers.Start(stopCh)
 
-	<-stopCh
-	klog.V(1).Infof("startControllers: received stopping signal, exiting")
-
-	return nil
+	select {}
 }
 
 // initFunc is used to launch a particular controller.  It may run additional "should I activate checks".
diff --git a/cmd/cloud-controller-manager/app/dynamic/watch_secret.go b/cmd/cloud-controller-manager/app/dynamic/watch_secret.go
deleted file mode 100644
index 884e9269f..000000000
--- a/cmd/cloud-controller-manager/app/dynamic/watch_secret.go
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
-Copyright 2021 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 dynamic
-
-import (
-	"fmt"
-	"os"
-	"strings"
-
-	v1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/util/wait"
-	"k8s.io/client-go/informers"
-	coreinformers "k8s.io/client-go/informers/core/v1"
-	"k8s.io/client-go/tools/cache"
-	"k8s.io/klog/v2"
-
-	cloudcontrollerconfig "sigs.k8s.io/cloud-provider-azure/cmd/cloud-controller-manager/app/config"
-	"sigs.k8s.io/cloud-provider-azure/cmd/cloud-controller-manager/app/options"
-)
-
-type SecretWatcher struct {
-	informerFactory informers.SharedInformerFactory
-	secretInformer  coreinformers.SecretInformer
-}
-
-// Run starts shared informers and waits for the shared informer cache to
-// synchronize.
-func (c *SecretWatcher) Run(stopCh <-chan struct{}) error {
-	c.informerFactory.Start(stopCh)
-
-	if !cache.WaitForCacheSync(stopCh, c.secretInformer.Informer().HasSynced) {
-		return fmt.Errorf("failed to operation the initial list of secrets")
-	}
-
-	return nil
-}
-
-func RunSecretWatcherOrDie(c *cloudcontrollerconfig.Config) chan struct{} {
-	factory := informers.NewSharedInformerFactory(c.VersionedClient, options.ResyncPeriod(c)())
-	secretWatcher, updateCh := NewSecretWatcher(factory, c.DynamicReloadingConfig.CloudConfigSecretName, c.DynamicReloadingConfig.CloudConfigSecretNamespace)
-	err := secretWatcher.Run(wait.NeverStop)
-	if err != nil {
-		klog.Errorf("Run: failed to initialize secret watcher: %v", err)
-		os.Exit(1)
-	}
-
-	return updateCh
-}
-
-// NewSecretWatcher creates a SecretWatcher and a signal channel to indicate
-// the specific secret has been updated
-func NewSecretWatcher(informerFactory informers.SharedInformerFactory, secretName, secretNamespace string) (*SecretWatcher, chan struct{}) {
-	secretInformer := informerFactory.Core().V1().Secrets()
-	updateSignal := make(chan struct{})
-
-	secretWatcher := &SecretWatcher{
-		informerFactory: informerFactory,
-		secretInformer:  secretInformer,
-	}
-	secretInformer.Informer().AddEventHandler(
-		// Your custom resource event handlers.
-		cache.ResourceEventHandlerFuncs{
-			UpdateFunc: func(oldObj, newObj interface{}) {
-				newSecret := newObj.(*v1.Secret)
-
-				if strings.EqualFold(newSecret.Name, secretName) &&
-					strings.EqualFold(newSecret.Namespace, secretNamespace) {
-					klog.V(1).Infof("secret %s updated, sending the signal", newSecret.Name)
-					updateSignal <- struct{}{}
-				}
-			},
-		},
-	)
-
-	return secretWatcher, updateSignal
-}
diff --git a/cmd/cloud-controller-manager/app/options/dynamic.go b/cmd/cloud-controller-manager/app/options/dynamic.go
deleted file mode 100644
index 05ee991d7..000000000
--- a/cmd/cloud-controller-manager/app/options/dynamic.go
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
-Copyright 2021 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 options
-
-import (
-	"github.com/spf13/pflag"
-
-	app "sigs.k8s.io/cloud-provider-azure/cmd/cloud-controller-manager/app/config"
-)
-
-// DynamicReloadingOptions holds the configurations of the dynamic reloading logics
-type DynamicReloadingOptions struct {
-	EnableDynamicReloading     bool
-	CloudConfigSecretName      string
-	CloudConfigSecretNamespace string
-	CloudConfigKey             string
-}
-
-// AddFlags adds flags releated to dynamic reloading for controller manager to the specified FlagSet
-func (o *DynamicReloadingOptions) AddFlags(fs *pflag.FlagSet) {
-	if o == nil {
-		return
-	}
-
-	fs.BoolVar(&o.EnableDynamicReloading, "enable-dynamic-reloading", false, "Enable re-configuring cloud controller manager from secret without restarting")
-	fs.StringVar(&o.CloudConfigSecretName, "cloud-config-secret-name", "cloud-provider-config", "The name of the cloud config secret, default to 'cloud-provider-config'")
-	fs.StringVar(&o.CloudConfigSecretNamespace, "cloud-config-secret-namespace", "kube-system", "The k8s namespace of the cloud config secret, default to 'kube-system'")
-	fs.StringVar(&o.CloudConfigKey, "cloud-config-key", "cloud-config", "The key of the config data in the cloud config secret, default to 'cloud-config'")
-}
-
-// ApplyTo fills up dynamic reloading config with options
-func (o *DynamicReloadingOptions) ApplyTo(cfg *app.DynamicReloadingConfig) error {
-	if o == nil {
-		return nil
-	}
-
-	cfg.EnableDynamicReloading = o.EnableDynamicReloading
-	cfg.CloudConfigSecretName = o.CloudConfigSecretName
-	cfg.CloudConfigSecretNamespace = o.CloudConfigSecretNamespace
-	cfg.CloudConfigKey = o.CloudConfigKey
-
-	return nil
-}
-
-// Validate checks validation of DynamicReloadingOptions
-func (o *DynamicReloadingOptions) Validate() []error {
-	return nil
-}
-
-func defaultDynamicReloadingOptions() *DynamicReloadingOptions {
-	return &DynamicReloadingOptions{
-		EnableDynamicReloading:     false,
-		CloudConfigSecretName:      "",
-		CloudConfigSecretNamespace: "",
-		CloudConfigKey:             "",
-	}
-}
diff --git a/cmd/cloud-controller-manager/app/options/options.go b/cmd/cloud-controller-manager/app/options/options.go
index 44fbe4f96..aa1f46b8a 100644
--- a/cmd/cloud-controller-manager/app/options/options.go
+++ b/cmd/cloud-controller-manager/app/options/options.go
@@ -80,8 +80,6 @@ type CloudControllerManagerOptions struct {
 
 	// NodeStatusUpdateFrequency is the frequency at which the controller updates nodes' status
 	NodeStatusUpdateFrequency metav1.Duration
-
-	DynamicReloading *DynamicReloadingOptions
 }
 
 // NewCloudControllerManagerOptions creates a new ExternalCMServer with a default config.
@@ -107,7 +105,6 @@ func NewCloudControllerManagerOptions() (*CloudControllerManagerOptions, error)
 		Authentication:            apiserveroptions.NewDelegatingAuthenticationOptions(),
 		Authorization:             apiserveroptions.NewDelegatingAuthorizationOptions(),
 		NodeStatusUpdateFrequency: componentConfig.NodeStatusUpdateFrequency,
-		DynamicReloading:          defaultDynamicReloadingOptions(),
 	}
 
 	s.Authentication.RemoteKubeConfigFileOptional = true
@@ -153,8 +150,6 @@ func (o *CloudControllerManagerOptions) Flags(allControllers, disabledByDefaultC
 	o.Authentication.AddFlags(fss.FlagSet("authentication"))
 	o.Authorization.AddFlags(fss.FlagSet("authorization"))
 
-	o.DynamicReloading.AddFlags(fss.FlagSet("dynamic reloading"))
-
 	fs := fss.FlagSet("misc")
 	fs.StringVar(&o.Master, "master", o.Master, "The address of the Kubernetes API server (overrides any value in kubeconfig).")
 	fs.StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, "Path to kubeconfig file with authorization and master location information.")
@@ -186,9 +181,6 @@ func (o *CloudControllerManagerOptions) ApplyTo(c *cloudcontrollerconfig.Config,
 	if err = o.SecureServing.ApplyTo(&c.SecureServing, &c.LoopbackClientConfig); err != nil {
 		return err
 	}
-	if err = o.DynamicReloading.ApplyTo(&c.DynamicReloadingConfig); err != nil {
-		return err
-	}
 	if o.SecureServing.BindPort != 0 || o.SecureServing.Listener != nil {
 		o.Authentication.RemoteKubeConfigFile = o.Kubeconfig
 		o.Authorization.RemoteKubeConfigFile = o.Kubeconfig
@@ -230,7 +222,7 @@ func (o *CloudControllerManagerOptions) ApplyTo(c *cloudcontrollerconfig.Config,
 		c.ClientBuilder = rootClientBuilder
 	}
 	c.VersionedClient = rootClientBuilder.ClientOrDie("shared-informers")
-	c.SharedInformers = informers.NewSharedInformerFactory(c.VersionedClient, ResyncPeriod(c)())
+	c.SharedInformers = informers.NewSharedInformerFactory(c.VersionedClient, resyncPeriod(c)())
 
 	// sync back to component config
 	// TODO: find more elegant way than syncing back the values.
@@ -254,7 +246,6 @@ func (o *CloudControllerManagerOptions) Validate(allControllers, disabledByDefau
 	errors = append(errors, o.InsecureServing.Validate()...)
 	errors = append(errors, o.Authentication.Validate()...)
 	errors = append(errors, o.Authorization.Validate()...)
-	errors = append(errors, o.DynamicReloading.Validate()...)
 
 	if len(o.KubeCloudShared.CloudProvider.Name) == 0 {
 		errors = append(errors, fmt.Errorf("--cloud-provider cannot be empty"))
@@ -264,15 +255,11 @@ func (o *CloudControllerManagerOptions) Validate(allControllers, disabledByDefau
 		errors = append(errors, fmt.Errorf("--concurrent-service-syncs is limited to 1 only"))
 	}
 
-	if !o.DynamicReloading.EnableDynamicReloading && o.KubeCloudShared.CloudProvider.CloudConfigFile == "" {
-		errors = append(errors, fmt.Errorf("--cloud-config cannot be empty when --enable-dynamic-reloading is not set to true"))
-	}
-
 	return utilerrors.NewAggregate(errors)
 }
 
-// ResyncPeriod computes the time interval a shared informer waits before resyncing with the api server
-func ResyncPeriod(c *cloudcontrollerconfig.Config) func() time.Duration {
+// resyncPeriod computes the time interval a shared informer waits before resyncing with the api server
+func resyncPeriod(c *cloudcontrollerconfig.Config) func() time.Duration {
 	return func() time.Duration {
 		factor := rand.Float64() + 1 // #nosec G404
 		return time.Duration(float64(c.ComponentConfig.Generic.MinResyncPeriod.Nanoseconds()) * factor)
diff --git a/pkg/consts/consts.go b/pkg/consts/consts.go
index ad045630d..d522a7759 100644
--- a/pkg/consts/consts.go
+++ b/pkg/consts/consts.go
@@ -169,6 +169,13 @@ const (
 	BackoffDurationDefault = 5 // in seconds
 	// BackoffJitterDefault is the default value of the backoff jitter
 	BackoffJitterDefault = 1.0
+
+	// CloudConfigNamespace is the namespace of the cloud config secret
+	CloudConfigNamespace = "kube-system"
+	// CloudConfigKey is the name of the cloud config key
+	CloudConfigKey = "cloud-config"
+	// CloudConfigSecName is the name of the cloud config secret
+	CloudConfigSecName = "azure-cloud-provider"
 )
 
 // load balancer
diff --git a/pkg/provider/azure.go b/pkg/provider/azure.go
index 7f8705067..29b1227ab 100644
--- a/pkg/provider/azure.go
+++ b/pkg/provider/azure.go
@@ -222,12 +222,6 @@ type Config struct {
 	RouteUpdateWaitingInSeconds int `json:"routeUpdateWaitingInSeconds,omitempty" yaml:"routeUpdateWaitingInSeconds,omitempty"`
 }
 
-type InitSecretConfig struct {
-	SecretName      string `json:"secretName,omitempty" yaml:"secret_name,omitempty"`
-	SecretNamespace string `json:"secret_namespace,omitempty" yaml:"secret_namespace,omitempty"`
-	CloudConfigKey  string `json:"cloud_config_key,omitempty" yaml:"cloud_config_key,omitempty"`
-}
-
 // HasExtendedLocation returns true if extendedlocation prop are specified.
 func (config *Config) HasExtendedLocation() bool {
 	return config.ExtendedLocationName != "" && config.ExtendedLocationType != ""
@@ -245,7 +239,6 @@ var (
 // Cloud holds the config and clients
 type Cloud struct {
 	Config
-	InitSecretConfig
 	Environment azure.Environment
 
 	RoutesClient                    routeclient.Interface
@@ -341,32 +334,6 @@ func NewCloud(configReader io.Reader) (cloudprovider.Interface, error) {
 	return az, nil
 }
 
-func NewCloudFromSecret(clientBuilder cloudprovider.ControllerClientBuilder, secretName, secretNamespace, cloudConfigKey string) (cloudprovider.Interface, error) {
-	az := &Cloud{
-		nodeNames:          sets.NewString(),
-		nodeZones:          map[string]sets.String{},
-		nodeResourceGroups: map[string]string{},
-		unmanagedNodes:     sets.NewString(),
-		routeCIDRs:         map[string]string{},
-		InitSecretConfig: InitSecretConfig{
-			SecretName:      secretName,
-			SecretNamespace: secretNamespace,
-			CloudConfigKey:  cloudConfigKey,
-		},
-	}
-
-	az.Initialize(clientBuilder, wait.NeverStop)
-
-	err := az.InitializeCloudFromSecret()
-	if err != nil {
-		return nil, fmt.Errorf("NewCloudFromSecret: failed to initialize cloud from secret %s/%s: %v", az.SecretNamespace, az.SecretName, err)
-	}
-
-	az.ipv6DualStackEnabled = utilfeature.DefaultFeatureGate.Enabled(consts.IPv6DualStack)
-
-	return az, nil
-}
-
 // NewCloudWithoutFeatureGates returns a Cloud without trying to wire the feature gates.  This is used by the unit tests
 // that don't load the actual features being used in the cluster.
 func NewCloudWithoutFeatureGates(configReader io.Reader) (*Cloud, error) {
@@ -393,9 +360,10 @@ func NewCloudWithoutFeatureGates(configReader io.Reader) (*Cloud, error) {
 
 // InitializeCloudFromConfig initializes the Cloud from config.
 func (az *Cloud) InitializeCloudFromConfig(config *Config, fromSecret bool) error {
+	// cloud-config not set, return nil so that it would be initialized from secret.
 	if config == nil {
-		// should not reach here
-		return fmt.Errorf("InitializeCloudFromConfig: cannot initialize from nil config")
+		klog.Warning("cloud-config is not provided, Azure cloud provider will be initialized from secret")
+		return nil
 	}
 
 	if config.RouteTableResourceGroup == "" {
@@ -750,6 +718,7 @@ func (az *Cloud) Initialize(clientBuilder cloudprovider.ControllerClientBuilder,
 	az.eventBroadcaster = record.NewBroadcaster()
 	az.eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: az.KubeClient.CoreV1().Events("")})
 	az.eventRecorder = az.eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "azure-cloud-provider"})
+	az.InitializeCloudFromSecret()
 }
 
 // LoadBalancer returns a balancer interface. Also returns true if the interface is supported, false otherwise.
diff --git a/pkg/provider/azure_config.go b/pkg/provider/azure_config.go
index 1fca4bdbe..67b4c4b52 100644
--- a/pkg/provider/azure_config.go
+++ b/pkg/provider/azure_config.go
@@ -23,6 +23,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/klog/v2"
 
+	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/yaml"
 )
 
@@ -39,24 +40,21 @@ const (
 )
 
 // InitializeCloudFromSecret initializes Azure cloud provider from Kubernetes secret.
-func (az *Cloud) InitializeCloudFromSecret() error {
+func (az *Cloud) InitializeCloudFromSecret() {
 	config, err := az.getConfigFromSecret()
 	if err != nil {
-		klog.Errorf("Failed to get cloud-config from secret: %v", err)
-		return fmt.Errorf("InitializeCloudFromSecret: failed to get cloud config from secret %s/%s: %w", az.SecretNamespace, az.SecretName, err)
+		klog.Warningf("Failed to get cloud-config from secret: %v, skip initializing from secret", err)
+		return
 	}
 
 	if config == nil {
 		// Skip re-initialization if the config is not override.
-		return nil
+		return
 	}
 
 	if err := az.InitializeCloudFromConfig(config, true); err != nil {
 		klog.Errorf("Failed to initialize Azure cloud provider: %v", err)
-		return fmt.Errorf("InitializeCloudFromSecret: failed to initialize Azure cloud provider: %w", err)
 	}
-
-	return nil
 }
 
 func (az *Cloud) getConfigFromSecret() (*Config, error) {
@@ -65,14 +63,14 @@ func (az *Cloud) getConfigFromSecret() (*Config, error) {
 		return nil, nil
 	}
 
-	secret, err := az.KubeClient.CoreV1().Secrets(az.SecretNamespace).Get(context.TODO(), az.SecretName, metav1.GetOptions{})
+	secret, err := az.KubeClient.CoreV1().Secrets(consts.CloudConfigNamespace).Get(context.TODO(), consts.CloudConfigSecName, metav1.GetOptions{})
 	if err != nil {
-		return nil, fmt.Errorf("failed to get secret %s/%s: %w", az.SecretNamespace, az.SecretName, err)
+		return nil, fmt.Errorf("failed to get secret %s: %w", consts.CloudConfigSecName, err)
 	}
 
-	cloudConfigData, ok := secret.Data[az.CloudConfigKey]
+	cloudConfigData, ok := secret.Data[consts.CloudConfigKey]
 	if !ok {
-		return nil, fmt.Errorf("cloud-config is not set in the secret (%s/%s)", az.SecretNamespace, az.SecretName)
+		return nil, fmt.Errorf("cloud-config is not set in the secret (%s)", consts.CloudConfigSecName)
 	}
 
 	config := Config{}
diff --git a/pkg/provider/azure_loadbalancer.go b/pkg/provider/azure_loadbalancer.go
index aface44e2..21512760b 100644
--- a/pkg/provider/azure_loadbalancer.go
+++ b/pkg/provider/azure_loadbalancer.go
@@ -1430,7 +1430,7 @@ func (az *Cloud) reconcileLoadBalancer(clusterName string, service *v1.Service,
 
 	existingLBs, err := az.reconcileSharedLoadBalancer(service, clusterName, nodes)
 	if err != nil {
-		klog.Errorf("reconcileLoadBalancer: failed to reconcile shared load balancer: %v", err)
+		klog.Errorf("reconcileLoadBalancer: failed to reconcile shared load balancer: %w", err)
 		return nil, err
 	}
 
diff --git a/site/content/en/install/configs.md b/site/content/en/install/configs.md
index 9eb5c022d..19ec5628a 100644
--- a/site/content/en/install/configs.md
+++ b/site/content/en/install/configs.md
@@ -93,7 +93,6 @@ If more than one value is set, the order is `Managed Identity` > `Service Princi
 |disableOutboundSNAT| Disable outbound SNAT for SLB | Default to false and available since v1.11.9, v1.12.7, v1.13.5 and v1.14.0|
 |maximumLoadBalancerRuleCount|Maximum allowed LoadBalancer Rule Count is the limit enforced by Azure Load balancer|Integer value, default to [148](https://github.com/kubernetes/kubernetes/blob/v1.10.0/pkg/cloudprovider/providers/azure/azure.go#L48)|
 |routeTableResourceGroup| The resource group name for routeTable | Default same as resourceGroup and available since v1.15.0 |
-|cloudConfigType| The cloud configure type for Azure cloud provider. Supported values are file, secret and merge.| Default to `merge`.  and available since v1.15.0 |
 |loadBalancerName| Working together with loadBalancerResourceGroup to determine the LB name in a different resource group | Since v1.18.0, default is cluster name setting on kube-controller-manager|
 |loadBalancerResourceGroup | The load balancer resource group name, which is different from node resource group | Since v1.18.0, default is same as resourceGroup|
 |disableAvailabilitySetNodes| Disable supporting for AvailabilitySet virtual machines in vmss cluster. It should be only used when vmType is "vmss" and all the nodes (including master) are VMSS virtual machines | Since v1.18.0, default is false|
@@ -130,13 +129,11 @@ By default, if nodes are labeled with `node-role.kubernetes.io/master`, they wou
 
 ### Setting Azure cloud provider from Kubernetes secrets
 
-Since v1.15.0, Azure cloud provider supports reading the cloud config from Kubernetes secrets. The secret is a serialized version of `azure.json` file with key `cloud-config`. The secret should be put in `kube-system` namespace and its name should be `azure-cloud-provider`.
+Since v1.21.0, Azure cloud provider supports reading the cloud config from Kubernetes secrets. The secret is a serialized version of `azure.json` file. When the secret is changed, the cloud controller manager will re-constructing itself without restarting the pod.
 
-To enable this feature, set `cloudConfigType` to `secret` or `merge` (default is `merge`). All supported values for this option are:
+To enable this feature, set `--enable-dynamic-reloading=true` and configure the secret name, namespace and data key by `--cloud-config-secret-name`, `--cloud-config-secret-namespace` and `--cloud-config-key`. When initializing from secret, the `--cloud-config` would be ignored.
 
-- `file`: The cloud provider configuration is read from cloud-config file.
-- `secret`: the cloud provider configuration must be overridden by the secret.
-- `merge`: the cloud provider configuration can be optionally overridden by a secret when it is set explicitly in the secret, this is default value.
+> Note that the `--enable-dynamic-reloading` cannot be `false` if `--cloud-config` is empty. To build the cloud provider from classic config file, please explicitly specify the `--cloud-config` and do not set `--enable-dynamic-reloading=true`. In this manner, the cloud controller manager will not be updated when the config file is changed. You need to restart the pod to manually trigger the re-initialization.
 
 Since Azure cloud provider would read Kubernetes secrets, the following RBAC should also be configured:
 


Test patch:
diff --git a/cmd/cloud-controller-manager/app/options/options_test.go b/cmd/cloud-controller-manager/app/options/options_test.go
index deb07a9d0..ec451e06c 100644
--- a/cmd/cloud-controller-manager/app/options/options_test.go
+++ b/cmd/cloud-controller-manager/app/options/options_test.go
@@ -139,12 +139,6 @@ func TestDefaultFlags(t *testing.T) {
 		Kubeconfig:                "",
 		Master:                    "",
 		NodeStatusUpdateFrequency: metav1.Duration{Duration: 5 * time.Minute},
-		DynamicReloading: &DynamicReloadingOptions{
-			EnableDynamicReloading:     false,
-			CloudConfigSecretName:      "",
-			CloudConfigSecretNamespace: "",
-			CloudConfigKey:             "",
-		},
 	}
 	if !reflect.DeepEqual(expected, s) {
 		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", diff.ObjectReflectDiff(expected, s))
@@ -189,8 +183,6 @@ func TestAddFlags(t *testing.T) {
 		"--route-reconciliation-period=30s",
 		"--secure-port=10001",
 		"--use-service-account-credentials=false",
-		"--enable-dynamic-reloading=true",
-		"--cloud-config-secret-name=test-secret",
 	}
 	err := fs.Parse(args)
 	if err != nil {
@@ -292,12 +284,6 @@ func TestAddFlags(t *testing.T) {
 		Kubeconfig:                "/kubeconfig",
 		Master:                    "192.168.4.20",
 		NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Minute},
-		DynamicReloading: &DynamicReloadingOptions{
-			EnableDynamicReloading:     true,
-			CloudConfigSecretName:      "test-secret",
-			CloudConfigSecretNamespace: "kube-system",
-			CloudConfigKey:             "cloud-config",
-		},
 	}
 	if !reflect.DeepEqual(expected, s) {
 		t.Errorf("Got different run options than expected.\nDifference detected on:\n%s", diff.ObjectReflectDiff(expected, s))
@@ -315,7 +301,6 @@ func TestValidate(t *testing.T) {
 			expected: "",
 			generateTestCloudControllerManagerOptions: func() *CloudControllerManagerOptions {
 				s, _ := NewCloudControllerManagerOptions()
-				s.DynamicReloading.EnableDynamicReloading = true
 				return s
 			},
 		},
@@ -325,7 +310,6 @@ func TestValidate(t *testing.T) {
 			generateTestCloudControllerManagerOptions: func() *CloudControllerManagerOptions {
 				s, _ := NewCloudControllerManagerOptions()
 				s.KubeCloudShared.CloudProvider.Name = ""
-				s.KubeCloudShared.CloudProvider.CloudConfigFile = "azure.json"
 				return s
 			},
 		},
@@ -335,15 +319,6 @@ func TestValidate(t *testing.T) {
 			generateTestCloudControllerManagerOptions: func() *CloudControllerManagerOptions {
 				s, _ := NewCloudControllerManagerOptions()
 				s.ServiceController.ConcurrentServiceSyncs = 10
-				s.KubeCloudShared.CloudProvider.CloudConfigFile = "azure.json"
-				return s
-			},
-		},
-		{
-			desc:     "should return an error if the cloud config file is empty and the dynamic reloading is not enabled",
-			expected: "--cloud-config cannot be empty when --enable-dynamic-reloading is not set to true",
-			generateTestCloudControllerManagerOptions: func() *CloudControllerManagerOptions {
-				s, _ := NewCloudControllerManagerOptions()
 				return s
 			},
 		},
diff --git a/cmd/cloud-controller-manager/app/testing/testserver.go b/cmd/cloud-controller-manager/app/testing/testserver.go
index 93da7b480..6bf08a1f4 100644
--- a/cmd/cloud-controller-manager/app/testing/testserver.go
+++ b/cmd/cloud-controller-manager/app/testing/testserver.go
@@ -120,7 +120,7 @@ func StartTestServer(t Logger, customFlags []string) (result TestServer, err err
 
 	errCh := make(chan error)
 	go func(stopCh <-chan struct{}) {
-		if err := app.Run(context.TODO(), config.Complete()); err != nil {
+		if err := app.Run(config.Complete(), stopCh); err != nil {
 			errCh <- err
 		}
 	}(stopCh)
diff --git a/pkg/provider/azure_config_test.go b/pkg/provider/azure_config_test.go
index 558427fd1..997d8d188 100644
--- a/pkg/provider/azure_config_test.go
+++ b/pkg/provider/azure_config_test.go
@@ -28,6 +28,7 @@ import (
 	fakeclient "k8s.io/client-go/kubernetes/fake"
 
 	"sigs.k8s.io/cloud-provider-azure/pkg/auth"
+	"sigs.k8s.io/cloud-provider-azure/pkg/consts"
 	"sigs.k8s.io/yaml"
 )
 
@@ -141,11 +142,6 @@ func TestGetConfigFromSecret(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			az := &Cloud{
 				KubeClient: fakeclient.NewSimpleClientset(),
-				InitSecretConfig: InitSecretConfig{
-					SecretName:      "azure-cloud-provider",
-					SecretNamespace: "kube-system",
-					CloudConfigKey:  "cloud-config",
-				},
 			}
 			if test.existingConfig != nil {
 				az.Config = *test.existingConfig
@@ -168,7 +164,7 @@ func TestGetConfigFromSecret(t *testing.T) {
 				if test.secretConfig == badConfig {
 					secret.Data = map[string][]byte{"cloud-config": []byte(`unknown: "hello",unknown: "hello"`)}
 				}
-				_, err := az.KubeClient.CoreV1().Secrets("kube-system").Create(context.TODO(), secret, metav1.CreateOptions{})
+				_, err := az.KubeClient.CoreV1().Secrets(consts.CloudConfigNamespace).Create(context.TODO(), secret, metav1.CreateOptions{})
 				assert.NoError(t, err, test.name)
 			}
 
@@ -212,7 +208,6 @@ func TestInitializeCloudFromSecret(t *testing.T) {
 			},
 			secretConfig: unknownConfigTypeConfig,
 			expected:     nil,
-			expectErr:    true,
 		},
 		{
 			name:           "Azure config should be override when cloud config type is secret",
@@ -243,11 +238,6 @@ func TestInitializeCloudFromSecret(t *testing.T) {
 		t.Run(test.name, func(t *testing.T) {
 			az := &Cloud{
 				KubeClient: fakeclient.NewSimpleClientset(),
-				InitSecretConfig: InitSecretConfig{
-					SecretName:      "azure-cloud-provider",
-					SecretNamespace: "kube-system",
-					CloudConfigKey:  "cloud-config",
-				},
 			}
 			if test.existingConfig != nil {
 				az.Config = *test.existingConfig
@@ -267,12 +257,11 @@ func TestInitializeCloudFromSecret(t *testing.T) {
 						"cloud-config": secretData,
 					}
 				}
-				_, err := az.KubeClient.CoreV1().Secrets("kube-system").Create(context.TODO(), secret, metav1.CreateOptions{})
+				_, err := az.KubeClient.CoreV1().Secrets(consts.CloudConfigNamespace).Create(context.TODO(), secret, metav1.CreateOptions{})
 				assert.NoError(t, err, test.name)
 			}
 
-			err := az.InitializeCloudFromSecret()
-			assert.Equal(t, test.expectErr, err != nil)
+			az.InitializeCloudFromSecret()
 		})
 	}
 }
diff --git a/pkg/provider/azure_test.go b/pkg/provider/azure_test.go
index 39741389a..1e0b91645 100644
--- a/pkg/provider/azure_test.go
+++ b/pkg/provider/azure_test.go
@@ -3296,7 +3296,7 @@ func TestInitializeCloudFromConfig(t *testing.T) {
 	az := GetTestCloud(ctrl)
 
 	err := az.InitializeCloudFromConfig(nil, false)
-	assert.Equal(t, fmt.Errorf("InitializeCloudFromConfig: cannot initialize from nil config"), err)
+	assert.NoError(t, err)
 
 	config := Config{
 		DisableAvailabilitySetNodes: true,