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  }