github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/cmd/dataprotection/main.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package main 21 22 import ( 23 "encoding/json" 24 "flag" 25 "fmt" 26 "os" 27 "strings" 28 29 // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 30 // to ensure that exec-entrypoint and run can make use of them. 31 "github.com/fsnotify/fsnotify" 32 snapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v3/apis/volumesnapshot/v1beta1" 33 snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" 34 "github.com/spf13/pflag" 35 corev1 "k8s.io/api/core/v1" 36 "k8s.io/apimachinery/pkg/runtime" 37 utilruntime "k8s.io/apimachinery/pkg/util/runtime" 38 discoverycli "k8s.io/client-go/discovery" 39 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 40 _ "k8s.io/client-go/plugin/pkg/client/auth" 41 ctrl "sigs.k8s.io/controller-runtime" 42 "sigs.k8s.io/controller-runtime/pkg/healthz" 43 "sigs.k8s.io/controller-runtime/pkg/log/zap" 44 45 // +kubebuilder:scaffold:imports 46 47 appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1" 48 dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1" 49 storagev1alpha1 "github.com/1aal/kubeblocks/apis/storage/v1alpha1" 50 dpcontrollers "github.com/1aal/kubeblocks/controllers/dataprotection" 51 "github.com/1aal/kubeblocks/pkg/constant" 52 intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil" 53 dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types" 54 viper "github.com/1aal/kubeblocks/pkg/viperx" 55 ) 56 57 // added lease.coordination.k8s.io for leader election 58 // +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch 59 60 const ( 61 appName = "kubeblocks" 62 ) 63 64 type flagName string 65 66 const ( 67 probeAddrFlagKey flagName = "health-probe-bind-address" 68 metricsAddrFlagKey flagName = "metrics-bind-address" 69 leaderElectFlagKey flagName = "leader-elect" 70 leaderElectIDFlagKey flagName = "leader-elect-id" 71 ) 72 73 var ( 74 scheme = runtime.NewScheme() 75 setupLog = ctrl.Log.WithName("setup") 76 ) 77 78 func init() { 79 utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 80 utilruntime.Must(appsv1alpha1.AddToScheme(scheme)) 81 utilruntime.Must(dpv1alpha1.AddToScheme(scheme)) 82 utilruntime.Must(snapshotv1.AddToScheme(scheme)) 83 utilruntime.Must(snapshotv1beta1.AddToScheme(scheme)) 84 utilruntime.Must(storagev1alpha1.AddToScheme(scheme)) 85 // +kubebuilder:scaffold:scheme 86 87 viper.SetConfigName("config") // name of config file (without extension) 88 viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name 89 viper.AddConfigPath(fmt.Sprintf("/etc/%s/", appName)) // path to look for the config file in 90 viper.AddConfigPath(fmt.Sprintf("$HOME/.%s", appName)) // call multiple times to append search path 91 viper.AddConfigPath(".") // optionally look for config in the working directory 92 viper.AutomaticEnv() 93 94 viper.SetDefault(constant.CfgKeyCtrlrReconcileRetryDurationMS, 1000) 95 viper.SetDefault("CERT_DIR", "/tmp/k8s-webhook-server/serving-certs") 96 viper.SetDefault("VOLUMESNAPSHOT_API_BETA", false) 97 viper.SetDefault(constant.KBToolsImage, "apecloud/kubeblocks-tools:latest") 98 viper.SetDefault("KUBEBLOCKS_SERVICEACCOUNT_NAME", "kubeblocks") 99 viper.SetDefault(constant.CfgKeyCtrlrMgrNS, "default") 100 viper.SetDefault(constant.KubernetesClusterDomainEnv, constant.DefaultDNSDomain) 101 viper.SetDefault(dptypes.CfgKeyGCFrequencySeconds, dptypes.DefaultGCFrequencySeconds) 102 } 103 104 func main() { 105 var ( 106 metricsAddr string 107 enableLeaderElection bool 108 enableLeaderElectionID string 109 probeAddr string 110 ) 111 112 flag.String(metricsAddrFlagKey.String(), ":8080", "The address the metric endpoint binds to.") 113 flag.String(probeAddrFlagKey.String(), ":8081", "The address the probe endpoint binds to.") 114 flag.Bool(leaderElectFlagKey.String(), false, 115 "Enable leader election for controller manager. "+ 116 "Enabling this will ensure there is only one active controller manager.") 117 118 flag.String(leaderElectIDFlagKey.String(), "abd03fda", 119 "The leader election ID prefix for controller manager. "+ 120 "This ID must be unique to controller manager.") 121 122 opts := zap.Options{ 123 Development: true, 124 } 125 opts.BindFlags(flag.CommandLine) 126 pflag.CommandLine.AddGoFlagSet(flag.CommandLine) 127 pflag.Parse() 128 129 // set normalizeFunc to replace flag name to viper name 130 normalizeFunc := pflag.CommandLine.GetNormalizeFunc() 131 pflag.CommandLine.SetNormalizeFunc(func(fs *pflag.FlagSet, name string) pflag.NormalizedName { 132 result := normalizeFunc(fs, name) 133 name = strings.ReplaceAll(string(result), "-", "_") 134 return pflag.NormalizedName(name) 135 }) 136 137 if err := viper.BindPFlags(pflag.CommandLine); err != nil { 138 setupLog.Error(err, "unable to bind flags") 139 os.Exit(1) 140 } 141 142 // NOTES: 143 // zap is "Blazing fast, structured, leveled logging in Go.", DON'T event try 144 // to refactor this logging lib to anything else. Check FAQ - https://github.com/uber-go/zap/blob/master/FAQ.md 145 ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 146 147 // Find and read the config file 148 if err := viper.ReadInConfig(); err != nil { // Handle errors reading the config file 149 setupLog.Info("unable to read in config, errors ignored") 150 } 151 setupLog.Info(fmt.Sprintf("config file: %s", viper.GetViper().ConfigFileUsed())) 152 viper.OnConfigChange(func(e fsnotify.Event) { 153 setupLog.Info(fmt.Sprintf("config file changed: %s", e.Name)) 154 }) 155 viper.WatchConfig() 156 157 metricsAddr = viper.GetString(metricsAddrFlagKey.viperName()) 158 probeAddr = viper.GetString(probeAddrFlagKey.viperName()) 159 enableLeaderElection = viper.GetBool(leaderElectFlagKey.viperName()) 160 enableLeaderElectionID = viper.GetString(leaderElectIDFlagKey.viperName()) 161 162 setupLog.Info(fmt.Sprintf("config settings: %v", viper.AllSettings())) 163 if err := validateRequiredToParseConfigs(); err != nil { 164 setupLog.Error(err, "config value error") 165 os.Exit(1) 166 } 167 168 mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 169 Scheme: scheme, 170 MetricsBindAddress: metricsAddr, 171 Port: 9443, 172 HealthProbeBindAddress: probeAddr, 173 LeaderElection: enableLeaderElection, 174 // NOTES: 175 // following LeaderElectionID is generated via hash/fnv (FNV-1 and FNV-1a), in 176 // pattern of '{{ hashFNV .Repo }}.{{ .Domain }}', make sure regenerate this ID 177 // if you have forked from this project template. 178 LeaderElectionID: enableLeaderElectionID + ".kubeblocks.io", 179 180 // NOTES: 181 // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily 182 // when the Manager ends. This requires the binary to immediately end when the 183 // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 184 // speeds up voluntary leader transitions as the new leader doesn't have to wait 185 // LeaseDuration time first. 186 // 187 // In the default scaffold provided, the program ends immediately after 188 // the manager stops, so would be fine to enable this option. However, 189 // if you are doing or intending to do any operation such as performing cleanups 190 // after the manager stops then its usage might be unsafe. 191 LeaderElectionReleaseOnCancel: true, 192 193 CertDir: viper.GetString("cert_dir"), 194 ClientDisableCacheFor: intctrlutil.GetUncachedObjects(), 195 }) 196 if err != nil { 197 setupLog.Error(err, "unable to start manager") 198 os.Exit(1) 199 } 200 201 if err = (&dpcontrollers.ActionSetReconciler{ 202 Client: mgr.GetClient(), 203 Scheme: mgr.GetScheme(), 204 Recorder: mgr.GetEventRecorderFor("actionset-controller"), 205 }).SetupWithManager(mgr); err != nil { 206 setupLog.Error(err, "unable to create controller", "controller", "ActionSet") 207 os.Exit(1) 208 } 209 210 if err = (&dpcontrollers.BackupReconciler{ 211 Client: mgr.GetClient(), 212 Scheme: mgr.GetScheme(), 213 Recorder: mgr.GetEventRecorderFor("backup-controller"), 214 RestConfig: mgr.GetConfig(), 215 }).SetupWithManager(mgr); err != nil { 216 setupLog.Error(err, "unable to create controller", "controller", "Backup") 217 os.Exit(1) 218 } 219 220 if err = (&dpcontrollers.RestoreReconciler{ 221 Client: mgr.GetClient(), 222 Scheme: mgr.GetScheme(), 223 Recorder: mgr.GetEventRecorderFor("restore-controller"), 224 }).SetupWithManager(mgr); err != nil { 225 setupLog.Error(err, "unable to create controller", "controller", "Restore") 226 os.Exit(1) 227 } 228 229 if err = (&dpcontrollers.VolumePopulatorReconciler{ 230 Client: mgr.GetClient(), 231 Scheme: mgr.GetScheme(), 232 Recorder: mgr.GetEventRecorderFor("volume-populator-controller"), 233 }).SetupWithManager(mgr); err != nil { 234 setupLog.Error(err, "unable to create controller", "controller", "VolumePopulator") 235 os.Exit(1) 236 } 237 238 if err = (&dpcontrollers.BackupPolicyReconciler{ 239 Client: mgr.GetClient(), 240 Scheme: mgr.GetScheme(), 241 Recorder: mgr.GetEventRecorderFor("backup-policy-controller"), 242 }).SetupWithManager(mgr); err != nil { 243 setupLog.Error(err, "unable to create controller", "controller", "BackupPolicy") 244 os.Exit(1) 245 } 246 247 if err = (&dpcontrollers.BackupScheduleReconciler{ 248 Client: mgr.GetClient(), 249 Scheme: mgr.GetScheme(), 250 Recorder: mgr.GetEventRecorderFor("backup-schedule-controller"), 251 }).SetupWithManager(mgr); err != nil { 252 setupLog.Error(err, "unable to create controller", "controller", "BackupSchedule") 253 os.Exit(1) 254 } 255 256 if err = (&dpcontrollers.BackupRepoReconciler{ 257 Client: mgr.GetClient(), 258 Scheme: mgr.GetScheme(), 259 Recorder: mgr.GetEventRecorderFor("backup-repo-controller"), 260 RestConfig: mgr.GetConfig(), 261 }).SetupWithManager(mgr); err != nil { 262 setupLog.Error(err, "unable to create controller", "controller", "BackupRepo") 263 os.Exit(1) 264 } 265 266 if err = dpcontrollers.NewGCReconciler(mgr).SetupWithManager(mgr); err != nil { 267 setupLog.Error(err, "unable to create controller", "controller", "GarbageCollection") 268 os.Exit(1) 269 } 270 271 // +kubebuilder:scaffold:builder 272 273 if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 274 setupLog.Error(err, "unable to set up health check") 275 os.Exit(1) 276 } 277 if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 278 setupLog.Error(err, "unable to set up ready check") 279 os.Exit(1) 280 } 281 282 cli, err := discoverycli.NewDiscoveryClientForConfig(mgr.GetConfig()) 283 if err != nil { 284 setupLog.Error(err, "unable to create discovery client") 285 os.Exit(1) 286 } 287 288 ver, err := cli.ServerVersion() 289 if err != nil { 290 setupLog.Error(err, "unable to discover version info") 291 os.Exit(1) 292 } 293 viper.SetDefault(constant.CfgKeyServerInfo, *ver) 294 295 setupLog.Info("starting manager") 296 if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 297 setupLog.Error(err, "problem running manager") 298 os.Exit(1) 299 } 300 } 301 302 func (r flagName) String() string { 303 return string(r) 304 } 305 306 func (r flagName) viperName() string { 307 return strings.ReplaceAll(r.String(), "-", "_") 308 } 309 310 func validateRequiredToParseConfigs() error { 311 validateTolerations := func(val string) error { 312 if val == "" { 313 return nil 314 } 315 var tolerations []corev1.Toleration 316 return json.Unmarshal([]byte(val), &tolerations) 317 } 318 319 validateAffinity := func(val string) error { 320 if val == "" { 321 return nil 322 } 323 affinity := corev1.Affinity{} 324 return json.Unmarshal([]byte(val), &affinity) 325 } 326 327 if err := validateTolerations(viper.GetString(constant.CfgKeyCtrlrMgrTolerations)); err != nil { 328 return err 329 } 330 if err := validateAffinity(viper.GetString(constant.CfgKeyCtrlrMgrAffinity)); err != nil { 331 return err 332 } 333 if cmNodeSelector := viper.GetString(constant.CfgKeyCtrlrMgrNodeSelector); cmNodeSelector != "" { 334 nodeSelector := map[string]string{} 335 if err := json.Unmarshal([]byte(cmNodeSelector), &nodeSelector); err != nil { 336 return err 337 } 338 } 339 return nil 340 }