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 }