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