sigs.k8s.io/cluster-api@v1.6.3/controlplane/kubeadm/main.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // main is the main package for the Kubeadm Control Plane provider.
    18  package main
    19  
    20  import (
    21  	"context"
    22  	"flag"
    23  	"fmt"
    24  	"os"
    25  	goruntime "runtime"
    26  	"time"
    27  
    28  	"github.com/spf13/pflag"
    29  	appsv1 "k8s.io/api/apps/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    32  	"k8s.io/apimachinery/pkg/labels"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/selection"
    35  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    36  	"k8s.io/client-go/tools/leaderelection/resourcelock"
    37  	cliflag "k8s.io/component-base/cli/flag"
    38  	"k8s.io/component-base/logs"
    39  	logsv1 "k8s.io/component-base/logs/api/v1"
    40  	_ "k8s.io/component-base/logs/json/register"
    41  	"k8s.io/klog/v2"
    42  	ctrl "sigs.k8s.io/controller-runtime"
    43  	"sigs.k8s.io/controller-runtime/pkg/cache"
    44  	"sigs.k8s.io/controller-runtime/pkg/client"
    45  	"sigs.k8s.io/controller-runtime/pkg/controller"
    46  	"sigs.k8s.io/controller-runtime/pkg/webhook"
    47  
    48  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    49  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    50  	"sigs.k8s.io/cluster-api/controllers/remote"
    51  	controlplanev1alpha4 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1alpha4"
    52  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    53  	kubeadmcontrolplanecontrollers "sigs.k8s.io/cluster-api/controlplane/kubeadm/controllers"
    54  	"sigs.k8s.io/cluster-api/controlplane/kubeadm/internal/etcd"
    55  	kcpwebhooks "sigs.k8s.io/cluster-api/controlplane/kubeadm/webhooks"
    56  	"sigs.k8s.io/cluster-api/feature"
    57  	controlplanev1alpha3 "sigs.k8s.io/cluster-api/internal/apis/controlplane/kubeadm/v1alpha3"
    58  	"sigs.k8s.io/cluster-api/util/flags"
    59  	"sigs.k8s.io/cluster-api/version"
    60  )
    61  
    62  var (
    63  	scheme         = runtime.NewScheme()
    64  	setupLog       = ctrl.Log.WithName("setup")
    65  	controllerName = "cluster-api-kubeadm-control-plane-manager"
    66  
    67  	// flags.
    68  	enableLeaderElection        bool
    69  	leaderElectionLeaseDuration time.Duration
    70  	leaderElectionRenewDeadline time.Duration
    71  	leaderElectionRetryPeriod   time.Duration
    72  	watchFilterValue            string
    73  	watchNamespace              string
    74  	profilerAddress             string
    75  	enableContentionProfiling   bool
    76  	syncPeriod                  time.Duration
    77  	restConfigQPS               float32
    78  	restConfigBurst             int
    79  	webhookPort                 int
    80  	webhookCertDir              string
    81  	healthAddr                  string
    82  	tlsOptions                  = flags.TLSOptions{}
    83  	diagnosticsOptions          = flags.DiagnosticsOptions{}
    84  	logOptions                  = logs.NewOptions()
    85  	// KCP specific flags.
    86  	kubeadmControlPlaneConcurrency int
    87  	clusterCacheTrackerConcurrency int
    88  	etcdDialTimeout                time.Duration
    89  	etcdCallTimeout                time.Duration
    90  )
    91  
    92  func init() {
    93  	_ = clientgoscheme.AddToScheme(scheme)
    94  	_ = clusterv1.AddToScheme(scheme)
    95  	_ = controlplanev1alpha3.AddToScheme(scheme)
    96  	_ = controlplanev1alpha4.AddToScheme(scheme)
    97  	_ = controlplanev1.AddToScheme(scheme)
    98  	_ = bootstrapv1.AddToScheme(scheme)
    99  	_ = apiextensionsv1.AddToScheme(scheme)
   100  }
   101  
   102  // InitFlags initializes the flags.
   103  func InitFlags(fs *pflag.FlagSet) {
   104  	logsv1.AddFlags(logOptions, fs)
   105  
   106  	fs.BoolVar(&enableLeaderElection, "leader-elect", false,
   107  		"Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.")
   108  
   109  	fs.DurationVar(&leaderElectionLeaseDuration, "leader-elect-lease-duration", 1*time.Minute,
   110  		"Interval at which non-leader candidates will wait to force acquire leadership (duration string)")
   111  
   112  	fs.DurationVar(&leaderElectionRenewDeadline, "leader-elect-renew-deadline", 40*time.Second,
   113  		"Duration that the leading controller manager will retry refreshing leadership before giving up (duration string)")
   114  
   115  	fs.DurationVar(&leaderElectionRetryPeriod, "leader-elect-retry-period", 5*time.Second,
   116  		"Duration the LeaderElector clients should wait between tries of actions (duration string)")
   117  
   118  	fs.StringVar(&watchNamespace, "namespace", "",
   119  		"Namespace that the controller watches to reconcile cluster-api objects. If unspecified, the controller watches for cluster-api objects across all namespaces.")
   120  
   121  	fs.StringVar(&watchFilterValue, "watch-filter", "",
   122  		fmt.Sprintf("Label value that the controller watches to reconcile cluster-api objects. Label key is always %s. If unspecified, the controller watches for all cluster-api objects.", clusterv1.WatchLabel))
   123  
   124  	fs.StringVar(&profilerAddress, "profiler-address", "",
   125  		"Bind address to expose the pprof profiler (e.g. localhost:6060)")
   126  
   127  	fs.BoolVar(&enableContentionProfiling, "contention-profiling", false,
   128  		"Enable block profiling")
   129  
   130  	fs.IntVar(&kubeadmControlPlaneConcurrency, "kubeadmcontrolplane-concurrency", 10,
   131  		"Number of kubeadm control planes to process simultaneously")
   132  
   133  	fs.IntVar(&clusterCacheTrackerConcurrency, "clustercachetracker-concurrency", 10,
   134  		"Number of clusters to process simultaneously")
   135  
   136  	fs.DurationVar(&syncPeriod, "sync-period", 10*time.Minute,
   137  		"The minimum interval at which watched resources are reconciled (e.g. 15m)")
   138  
   139  	fs.Float32Var(&restConfigQPS, "kube-api-qps", 20,
   140  		"Maximum queries per second from the controller client to the Kubernetes API server. Defaults to 20")
   141  
   142  	fs.IntVar(&restConfigBurst, "kube-api-burst", 30,
   143  		"Maximum number of queries that should be allowed in one burst from the controller client to the Kubernetes API server. Default 30")
   144  
   145  	fs.IntVar(&webhookPort, "webhook-port", 9443,
   146  		"Webhook Server port")
   147  
   148  	fs.StringVar(&webhookCertDir, "webhook-cert-dir", "/tmp/k8s-webhook-server/serving-certs/",
   149  		"Webhook cert dir, only used when webhook-port is specified.")
   150  
   151  	fs.StringVar(&healthAddr, "health-addr", ":9440",
   152  		"The address the health endpoint binds to.")
   153  
   154  	fs.DurationVar(&etcdDialTimeout, "etcd-dial-timeout-duration", 10*time.Second,
   155  		"Duration that the etcd client waits at most to establish a connection with etcd")
   156  
   157  	fs.DurationVar(&etcdCallTimeout, "etcd-call-timeout-duration", etcd.DefaultCallTimeout,
   158  		"Duration that the etcd client waits at most for read and write operations to etcd.")
   159  
   160  	flags.AddDiagnosticsOptions(fs, &diagnosticsOptions)
   161  	flags.AddTLSOptions(fs, &tlsOptions)
   162  
   163  	feature.MutableGates.AddFlag(fs)
   164  }
   165  
   166  // Add RBAC for the authorized diagnostics endpoint.
   167  // +kubebuilder:rbac:groups=authentication.k8s.io,resources=tokenreviews,verbs=create
   168  // +kubebuilder:rbac:groups=authorization.k8s.io,resources=subjectaccessreviews,verbs=create
   169  
   170  func main() {
   171  	InitFlags(pflag.CommandLine)
   172  	pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)
   173  	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
   174  	// Set log level 2 as default.
   175  	if err := pflag.CommandLine.Set("v", "2"); err != nil {
   176  		setupLog.Error(err, "failed to set log level: %v")
   177  		os.Exit(1)
   178  	}
   179  	pflag.Parse()
   180  
   181  	if err := logsv1.ValidateAndApply(logOptions, nil); err != nil {
   182  		setupLog.Error(err, "unable to start manager")
   183  		os.Exit(1)
   184  	}
   185  
   186  	// klog.Background will automatically use the right logger.
   187  	ctrl.SetLogger(klog.Background())
   188  
   189  	restConfig := ctrl.GetConfigOrDie()
   190  	restConfig.QPS = restConfigQPS
   191  	restConfig.Burst = restConfigBurst
   192  	restConfig.UserAgent = remote.DefaultClusterAPIUserAgent(controllerName)
   193  
   194  	tlsOptionOverrides, err := flags.GetTLSOptionOverrideFuncs(tlsOptions)
   195  	if err != nil {
   196  		setupLog.Error(err, "unable to add TLS settings to the webhook server")
   197  		os.Exit(1)
   198  	}
   199  
   200  	diagnosticsOpts := flags.GetDiagnosticsOptions(diagnosticsOptions)
   201  
   202  	var watchNamespaces map[string]cache.Config
   203  	if watchNamespace != "" {
   204  		watchNamespaces = map[string]cache.Config{
   205  			watchNamespace: {},
   206  		}
   207  	}
   208  
   209  	if enableContentionProfiling {
   210  		goruntime.SetBlockProfileRate(1)
   211  	}
   212  
   213  	req, _ := labels.NewRequirement(clusterv1.ClusterNameLabel, selection.Exists, nil)
   214  	clusterSecretCacheSelector := labels.NewSelector().Add(*req)
   215  
   216  	ctrlOptions := ctrl.Options{
   217  		Scheme:                     scheme,
   218  		LeaderElection:             enableLeaderElection,
   219  		LeaderElectionID:           "kubeadm-control-plane-manager-leader-election-capi",
   220  		LeaseDuration:              &leaderElectionLeaseDuration,
   221  		RenewDeadline:              &leaderElectionRenewDeadline,
   222  		RetryPeriod:                &leaderElectionRetryPeriod,
   223  		LeaderElectionResourceLock: resourcelock.LeasesResourceLock,
   224  		HealthProbeBindAddress:     healthAddr,
   225  		PprofBindAddress:           profilerAddress,
   226  		Metrics:                    diagnosticsOpts,
   227  		Cache: cache.Options{
   228  			DefaultNamespaces: watchNamespaces,
   229  			SyncPeriod:        &syncPeriod,
   230  			ByObject: map[client.Object]cache.ByObject{
   231  				// Note: Only Secrets with the cluster name label are cached.
   232  				// The default client of the manager won't use the cache for secrets at all (see Client.Cache.DisableFor).
   233  				// The cached secrets will only be used by the secretCachingClient we create below.
   234  				&corev1.Secret{}: {
   235  					Label: clusterSecretCacheSelector,
   236  				},
   237  			},
   238  		},
   239  		Client: client.Options{
   240  			Cache: &client.CacheOptions{
   241  				DisableFor: []client.Object{
   242  					&corev1.ConfigMap{},
   243  					&corev1.Secret{},
   244  				},
   245  				// This config ensures that the default client caches Unstructured objects.
   246  				// KCP is only using Unstructured to retrieve InfraMachines and InfraMachineTemplates.
   247  				// As the cache should be used in those cases, caching is configured globally instead of
   248  				// creating a separate client that caches Unstructured.
   249  				Unstructured: true,
   250  			},
   251  		},
   252  		WebhookServer: webhook.NewServer(
   253  			webhook.Options{
   254  				Port:    webhookPort,
   255  				CertDir: webhookCertDir,
   256  				TLSOpts: tlsOptionOverrides,
   257  			},
   258  		),
   259  	}
   260  
   261  	mgr, err := ctrl.NewManager(restConfig, ctrlOptions)
   262  	if err != nil {
   263  		setupLog.Error(err, "unable to start manager")
   264  		os.Exit(1)
   265  	}
   266  
   267  	// Setup the context that's going to be used in controllers and for the manager.
   268  	ctx := ctrl.SetupSignalHandler()
   269  
   270  	setupChecks(mgr)
   271  	setupReconcilers(ctx, mgr)
   272  	setupWebhooks(mgr)
   273  
   274  	setupLog.Info("starting manager", "version", version.Get().String())
   275  	if err := mgr.Start(ctx); err != nil {
   276  		setupLog.Error(err, "problem running manager")
   277  		os.Exit(1)
   278  	}
   279  }
   280  
   281  func setupChecks(mgr ctrl.Manager) {
   282  	if err := mgr.AddReadyzCheck("webhook", mgr.GetWebhookServer().StartedChecker()); err != nil {
   283  		setupLog.Error(err, "unable to create ready check")
   284  		os.Exit(1)
   285  	}
   286  
   287  	if err := mgr.AddHealthzCheck("webhook", mgr.GetWebhookServer().StartedChecker()); err != nil {
   288  		setupLog.Error(err, "unable to create health check")
   289  		os.Exit(1)
   290  	}
   291  }
   292  
   293  func setupReconcilers(ctx context.Context, mgr ctrl.Manager) {
   294  	secretCachingClient, err := client.New(mgr.GetConfig(), client.Options{
   295  		HTTPClient: mgr.GetHTTPClient(),
   296  		Cache: &client.CacheOptions{
   297  			Reader: mgr.GetCache(),
   298  		},
   299  	})
   300  	if err != nil {
   301  		setupLog.Error(err, "unable to create secret caching client")
   302  		os.Exit(1)
   303  	}
   304  
   305  	// Set up a ClusterCacheTracker to provide to controllers
   306  	// requiring a connection to a remote cluster
   307  	tracker, err := remote.NewClusterCacheTracker(mgr, remote.ClusterCacheTrackerOptions{
   308  		SecretCachingClient: secretCachingClient,
   309  		ControllerName:      controllerName,
   310  		Log:                 &ctrl.Log,
   311  		ClientUncachedObjects: []client.Object{
   312  			&corev1.ConfigMap{},
   313  			&corev1.Secret{},
   314  			&corev1.Pod{},
   315  			&appsv1.Deployment{},
   316  			&appsv1.DaemonSet{},
   317  		},
   318  	})
   319  	if err != nil {
   320  		setupLog.Error(err, "unable to create cluster cache tracker")
   321  		os.Exit(1)
   322  	}
   323  	if err := (&remote.ClusterCacheReconciler{
   324  		Client:           mgr.GetClient(),
   325  		Tracker:          tracker,
   326  		WatchFilterValue: watchFilterValue,
   327  	}).SetupWithManager(ctx, mgr, concurrency(clusterCacheTrackerConcurrency)); err != nil {
   328  		setupLog.Error(err, "unable to create controller", "controller", "ClusterCacheReconciler")
   329  		os.Exit(1)
   330  	}
   331  
   332  	if err := (&kubeadmcontrolplanecontrollers.KubeadmControlPlaneReconciler{
   333  		Client:              mgr.GetClient(),
   334  		SecretCachingClient: secretCachingClient,
   335  		Tracker:             tracker,
   336  		WatchFilterValue:    watchFilterValue,
   337  		EtcdDialTimeout:     etcdDialTimeout,
   338  		EtcdCallTimeout:     etcdCallTimeout,
   339  	}).SetupWithManager(ctx, mgr, concurrency(kubeadmControlPlaneConcurrency)); err != nil {
   340  		setupLog.Error(err, "unable to create controller", "controller", "KubeadmControlPlane")
   341  		os.Exit(1)
   342  	}
   343  }
   344  
   345  func setupWebhooks(mgr ctrl.Manager) {
   346  	if err := (&kcpwebhooks.KubeadmControlPlane{}).SetupWebhookWithManager(mgr); err != nil {
   347  		setupLog.Error(err, "unable to create webhook", "webhook", "KubeadmControlPlane")
   348  		os.Exit(1)
   349  	}
   350  
   351  	if err := (&kcpwebhooks.ScaleValidator{
   352  		Client: mgr.GetClient(),
   353  	}).SetupWebhookWithManager(mgr); err != nil {
   354  		setupLog.Error(err, "unable to create webhook", "webhook", "KubeadmControlPlane scale")
   355  		os.Exit(1)
   356  	}
   357  
   358  	if err := (&kcpwebhooks.KubeadmControlPlaneTemplate{}).SetupWebhookWithManager(mgr); err != nil {
   359  		setupLog.Error(err, "unable to create webhook", "webhook", "KubeadmControlPlaneTemplate")
   360  		os.Exit(1)
   361  	}
   362  }
   363  
   364  func concurrency(c int) controller.Options {
   365  	return controller.Options{MaxConcurrentReconciles: c}
   366  }