github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/cmd/manager/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  	"time"
    29  
    30  	// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
    31  	// to ensure that exec-entrypoint and run can make use of them.
    32  	"github.com/fsnotify/fsnotify"
    33  	snapshotv1beta1 "github.com/kubernetes-csi/external-snapshotter/client/v3/apis/volumesnapshot/v1beta1"
    34  	snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
    35  	"github.com/spf13/pflag"
    36  	corev1 "k8s.io/api/core/v1"
    37  	"k8s.io/apimachinery/pkg/runtime"
    38  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    39  	discoverycli "k8s.io/client-go/discovery"
    40  	clientgoscheme "k8s.io/client-go/kubernetes/scheme"
    41  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    42  	ctrl "sigs.k8s.io/controller-runtime"
    43  	"sigs.k8s.io/controller-runtime/pkg/healthz"
    44  	"sigs.k8s.io/controller-runtime/pkg/log/zap"
    45  
    46  	// +kubebuilder:scaffold:imports
    47  
    48  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    49  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    50  	extensionsv1alpha1 "github.com/1aal/kubeblocks/apis/extensions/v1alpha1"
    51  	storagev1alpha1 "github.com/1aal/kubeblocks/apis/storage/v1alpha1"
    52  	workloadsv1alpha1 "github.com/1aal/kubeblocks/apis/workloads/v1alpha1"
    53  	appscontrollers "github.com/1aal/kubeblocks/controllers/apps"
    54  	"github.com/1aal/kubeblocks/controllers/apps/configuration"
    55  	extensionscontrollers "github.com/1aal/kubeblocks/controllers/extensions"
    56  	k8scorecontrollers "github.com/1aal/kubeblocks/controllers/k8score"
    57  	storagecontrollers "github.com/1aal/kubeblocks/controllers/storage"
    58  	workloadscontrollers "github.com/1aal/kubeblocks/controllers/workloads"
    59  	"github.com/1aal/kubeblocks/pkg/constant"
    60  	"github.com/1aal/kubeblocks/pkg/controller/rsm"
    61  	intctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    62  	viper "github.com/1aal/kubeblocks/pkg/viperx"
    63  )
    64  
    65  // added lease.coordination.k8s.io for leader election
    66  // +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;watch;create;update;patch
    67  
    68  const (
    69  	appName = "kubeblocks"
    70  )
    71  
    72  var (
    73  	scheme   = runtime.NewScheme()
    74  	setupLog = ctrl.Log.WithName("setup")
    75  )
    76  
    77  func init() {
    78  	utilruntime.Must(clientgoscheme.AddToScheme(scheme))
    79  
    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(extensionsv1alpha1.AddToScheme(scheme))
    85  	utilruntime.Must(workloadsv1alpha1.AddToScheme(scheme))
    86  	utilruntime.Must(storagev1alpha1.AddToScheme(scheme))
    87  	// +kubebuilder:scaffold:scheme
    88  
    89  	viper.SetConfigName("config")                          // name of config file (without extension)
    90  	viper.SetConfigType("yaml")                            // REQUIRED if the config file does not have the extension in the name
    91  	viper.AddConfigPath(fmt.Sprintf("/etc/%s/", appName))  // path to look for the config file in
    92  	viper.AddConfigPath(fmt.Sprintf("$HOME/.%s", appName)) // call multiple times to append search path
    93  	viper.AddConfigPath(".")                               // optionally look for config in the working directory
    94  	viper.AutomaticEnv()
    95  
    96  	viper.SetDefault(constant.CfgKeyCtrlrReconcileRetryDurationMS, 1000)
    97  	viper.SetDefault("CERT_DIR", "/tmp/k8s-webhook-server/serving-certs")
    98  	viper.SetDefault(constant.EnableRBACManager, true)
    99  	viper.SetDefault("VOLUMESNAPSHOT_API_BETA", false)
   100  	viper.SetDefault(constant.KBToolsImage, "apecloud/kubeblocks-tools:latest")
   101  	viper.SetDefault("PROBE_SERVICE_HTTP_PORT", 3501)
   102  	viper.SetDefault("PROBE_SERVICE_GRPC_PORT", 50001)
   103  	viper.SetDefault("PROBE_SERVICE_LOG_LEVEL", "info")
   104  	viper.SetDefault("KUBEBLOCKS_SERVICEACCOUNT_NAME", "kubeblocks")
   105  	viper.SetDefault("CONFIG_MANAGER_GRPC_PORT", 9901)
   106  	viper.SetDefault("CONFIG_MANAGER_LOG_LEVEL", "info")
   107  	viper.SetDefault(constant.CfgKeyCtrlrMgrNS, "default")
   108  	viper.SetDefault(constant.FeatureGateReplicatedStateMachine, true)
   109  	viper.SetDefault(constant.KBDataScriptClientsImage, "apecloud/kubeblocks-datascript:latest")
   110  	viper.SetDefault(constant.KubernetesClusterDomainEnv, constant.DefaultDNSDomain)
   111  	viper.SetDefault(rsm.FeatureGateRSMCompatibilityMode, true)
   112  }
   113  
   114  type flagName string
   115  
   116  const (
   117  	probeAddrFlagKey     flagName = "health-probe-bind-address"
   118  	metricsAddrFlagKey   flagName = "metrics-bind-address"
   119  	leaderElectFlagKey   flagName = "leader-elect"
   120  	leaderElectIDFlagKey flagName = "leader-elect-id"
   121  
   122  	// switch flags key for API groups
   123  	appsFlagKey       flagName = "apps"
   124  	extensionsFlagKey flagName = "extensions"
   125  	workloadsFlagKey  flagName = "workloads"
   126  	storageFlagKey    flagName = "storage"
   127  )
   128  
   129  func (r flagName) String() string {
   130  	return string(r)
   131  }
   132  
   133  func (r flagName) viperName() string {
   134  	return strings.ReplaceAll(r.String(), "-", "_")
   135  }
   136  
   137  func validateRequiredToParseConfigs() error {
   138  	validateTolerations := func(val string) error {
   139  		if val == "" {
   140  			return nil
   141  		}
   142  		var tolerations []corev1.Toleration
   143  		return json.Unmarshal([]byte(val), &tolerations)
   144  	}
   145  
   146  	validateAffinity := func(val string) error {
   147  		if val == "" {
   148  			return nil
   149  		}
   150  		affinity := corev1.Affinity{}
   151  		return json.Unmarshal([]byte(val), &affinity)
   152  	}
   153  
   154  	if jobTTL := viper.GetString(constant.CfgKeyAddonJobTTL); jobTTL != "" {
   155  		if _, err := time.ParseDuration(jobTTL); err != nil {
   156  			return err
   157  		}
   158  	}
   159  	if err := validateTolerations(viper.GetString(constant.CfgKeyCtrlrMgrTolerations)); err != nil {
   160  		return err
   161  	}
   162  	if err := validateAffinity(viper.GetString(constant.CfgKeyCtrlrMgrAffinity)); err != nil {
   163  		return err
   164  	}
   165  	if cmNodeSelector := viper.GetString(constant.CfgKeyCtrlrMgrNodeSelector); cmNodeSelector != "" {
   166  		nodeSelector := map[string]string{}
   167  		if err := json.Unmarshal([]byte(cmNodeSelector), &nodeSelector); err != nil {
   168  			return err
   169  		}
   170  	}
   171  	if err := validateTolerations(viper.GetString(constant.CfgKeyDataPlaneTolerations)); err != nil {
   172  		return err
   173  	}
   174  	if err := validateAffinity(viper.GetString(constant.CfgKeyDataPlaneAffinity)); err != nil {
   175  		return err
   176  	}
   177  	return nil
   178  }
   179  
   180  func main() {
   181  	var metricsAddr string
   182  	var enableLeaderElection bool
   183  	var enableLeaderElectionID string
   184  	var probeAddr string
   185  	flag.String(metricsAddrFlagKey.String(), ":8080", "The address the metric endpoint binds to.")
   186  	flag.String(probeAddrFlagKey.String(), ":8081", "The address the probe endpoint binds to.")
   187  	flag.Bool(leaderElectFlagKey.String(), false,
   188  		"Enable leader election for controller manager. "+
   189  			"Enabling this will ensure there is only one active controller manager.")
   190  
   191  	flag.String(leaderElectIDFlagKey.String(), "001c317f",
   192  		"The leader election ID prefix for controller manager. "+
   193  			"This ID must be unique to controller manager.")
   194  
   195  	flag.Bool(appsFlagKey.String(), true,
   196  		"Enable the apps controller manager.")
   197  	flag.Bool(extensionsFlagKey.String(), true,
   198  		"Enable the extensions controller manager. ")
   199  	flag.Bool(workloadsFlagKey.String(), true,
   200  		"Enable the workloads controller manager. ")
   201  	flag.Bool(storageFlagKey.String(), true,
   202  		"Enable the storage controller manager. ")
   203  
   204  	opts := zap.Options{
   205  		Development: true,
   206  	}
   207  	opts.BindFlags(flag.CommandLine)
   208  	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
   209  	pflag.Parse()
   210  
   211  	// set normalizeFunc to replace flag name to viper name
   212  	normalizeFunc := pflag.CommandLine.GetNormalizeFunc()
   213  	pflag.CommandLine.SetNormalizeFunc(func(fs *pflag.FlagSet, name string) pflag.NormalizedName {
   214  		result := normalizeFunc(fs, name)
   215  		name = strings.ReplaceAll(string(result), "-", "_")
   216  		return pflag.NormalizedName(name)
   217  	})
   218  
   219  	if err := viper.BindPFlags(pflag.CommandLine); err != nil {
   220  		setupLog.Error(err, "unable to bind flags")
   221  		os.Exit(1)
   222  	}
   223  
   224  	// NOTES:
   225  	// zap is "Blazing fast, structured, leveled logging in Go.", DON'T event try
   226  	// to refactor this logging lib to anything else. Check FAQ - https://github.com/uber-go/zap/blob/master/FAQ.md
   227  	ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
   228  
   229  	// Find and read the config file
   230  	if err := viper.ReadInConfig(); err != nil { // Handle errors reading the config file
   231  		setupLog.Info("unable to read in config, errors ignored")
   232  	}
   233  	setupLog.Info(fmt.Sprintf("config file: %s", viper.GetViper().ConfigFileUsed()))
   234  	viper.OnConfigChange(func(e fsnotify.Event) {
   235  		setupLog.Info(fmt.Sprintf("config file changed: %s", e.Name))
   236  	})
   237  	viper.WatchConfig()
   238  
   239  	metricsAddr = viper.GetString(metricsAddrFlagKey.viperName())
   240  	probeAddr = viper.GetString(probeAddrFlagKey.viperName())
   241  	enableLeaderElection = viper.GetBool(leaderElectFlagKey.viperName())
   242  	enableLeaderElectionID = viper.GetString(leaderElectIDFlagKey.viperName())
   243  
   244  	setupLog.Info(fmt.Sprintf("config settings: %v", viper.AllSettings()))
   245  	if err := validateRequiredToParseConfigs(); err != nil {
   246  		setupLog.Error(err, "config value error")
   247  		os.Exit(1)
   248  	}
   249  
   250  	mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
   251  		Scheme:                 scheme,
   252  		MetricsBindAddress:     metricsAddr,
   253  		Port:                   9443,
   254  		HealthProbeBindAddress: probeAddr,
   255  		LeaderElection:         enableLeaderElection,
   256  		// NOTES:
   257  		// following LeaderElectionID is generated via hash/fnv (FNV-1 and FNV-1a), in
   258  		// pattern of '{{ hashFNV .Repo }}.{{ .Domain }}', make sure regenerate this ID
   259  		// if you have forked from this project template.
   260  		LeaderElectionID: enableLeaderElectionID + ".kubeblocks.io",
   261  
   262  		// NOTES:
   263  		// LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
   264  		// when the Manager ends. This requires the binary to immediately end when the
   265  		// Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
   266  		// speeds up voluntary leader transitions as the new leader doesn't have to wait
   267  		// LeaseDuration time first.
   268  		//
   269  		// In the default scaffold provided, the program ends immediately after
   270  		// the manager stops, so would be fine to enable this option. However,
   271  		// if you are doing or intending to do any operation such as performing cleanups
   272  		// after the manager stops then its usage might be unsafe.
   273  		LeaderElectionReleaseOnCancel: true,
   274  
   275  		CertDir:               viper.GetString("cert_dir"),
   276  		ClientDisableCacheFor: intctrlutil.GetUncachedObjects(),
   277  	})
   278  	if err != nil {
   279  		setupLog.Error(err, "unable to start manager")
   280  		os.Exit(1)
   281  	}
   282  
   283  	if viper.GetBool(appsFlagKey.viperName()) {
   284  		if err = (&appscontrollers.ClusterReconciler{
   285  			Client:   mgr.GetClient(),
   286  			Scheme:   mgr.GetScheme(),
   287  			Recorder: mgr.GetEventRecorderFor("cluster-controller"),
   288  		}).SetupWithManager(mgr); err != nil {
   289  			setupLog.Error(err, "unable to create controller", "controller", "Cluster")
   290  			os.Exit(1)
   291  		}
   292  
   293  		if err = (&appscontrollers.ClusterDefinitionReconciler{
   294  			Client:   mgr.GetClient(),
   295  			Scheme:   mgr.GetScheme(),
   296  			Recorder: mgr.GetEventRecorderFor("cluster-definition-controller"),
   297  		}).SetupWithManager(mgr); err != nil {
   298  			setupLog.Error(err, "unable to create controller", "controller", "ClusterDefinition")
   299  			os.Exit(1)
   300  		}
   301  
   302  		if err = (&appscontrollers.ClusterVersionReconciler{
   303  			Client:   mgr.GetClient(),
   304  			Scheme:   mgr.GetScheme(),
   305  			Recorder: mgr.GetEventRecorderFor("cluster-version-controller"),
   306  		}).SetupWithManager(mgr); err != nil {
   307  			setupLog.Error(err, "unable to create controller", "controller", "ClusterVersion")
   308  			os.Exit(1)
   309  		}
   310  
   311  		if err = (&appscontrollers.OpsRequestReconciler{
   312  			Client:   mgr.GetClient(),
   313  			Scheme:   mgr.GetScheme(),
   314  			Recorder: mgr.GetEventRecorderFor("ops-request-controller"),
   315  		}).SetupWithManager(mgr); err != nil {
   316  			setupLog.Error(err, "unable to create controller", "controller", "OpsRequest")
   317  			os.Exit(1)
   318  		}
   319  
   320  		if err = (&configuration.ConfigConstraintReconciler{
   321  			Client:   mgr.GetClient(),
   322  			Scheme:   mgr.GetScheme(),
   323  			Recorder: mgr.GetEventRecorderFor("config-constraint-controller"),
   324  		}).SetupWithManager(mgr); err != nil {
   325  			setupLog.Error(err, "unable to create controller", "controller", "ConfigConstraint")
   326  			os.Exit(1)
   327  		}
   328  
   329  		if err = (&configuration.ReconfigureReconciler{
   330  			Client:   mgr.GetClient(),
   331  			Scheme:   mgr.GetScheme(),
   332  			Recorder: mgr.GetEventRecorderFor("reconfigure-controller"),
   333  		}).SetupWithManager(mgr); err != nil {
   334  			setupLog.Error(err, "unable to create controller", "controller", "ReconfigureRequest")
   335  			os.Exit(1)
   336  		}
   337  
   338  		if err = (&configuration.ConfigurationReconciler{
   339  			Client:   mgr.GetClient(),
   340  			Scheme:   mgr.GetScheme(),
   341  			Recorder: mgr.GetEventRecorderFor("configuration-controller"),
   342  		}).SetupWithManager(mgr); err != nil {
   343  			setupLog.Error(err, "unable to create controller", "controller", "Configuration")
   344  			os.Exit(1)
   345  		}
   346  
   347  		if err = (&appscontrollers.SystemAccountReconciler{
   348  			Client:   mgr.GetClient(),
   349  			Scheme:   mgr.GetScheme(),
   350  			Recorder: mgr.GetEventRecorderFor("system-account-controller"),
   351  		}).SetupWithManager(mgr); err != nil {
   352  			setupLog.Error(err, "unable to create controller", "controller", "SystemAccount")
   353  			os.Exit(1)
   354  		}
   355  
   356  		if err = (&k8scorecontrollers.EventReconciler{
   357  			Client:   mgr.GetClient(),
   358  			Scheme:   mgr.GetScheme(),
   359  			Recorder: mgr.GetEventRecorderFor("event-controller"),
   360  		}).SetupWithManager(mgr); err != nil {
   361  			setupLog.Error(err, "unable to create controller", "controller", "Event")
   362  			os.Exit(1)
   363  		}
   364  
   365  		if err = (&appscontrollers.ComponentClassReconciler{
   366  			Client:   mgr.GetClient(),
   367  			Scheme:   mgr.GetScheme(),
   368  			Recorder: mgr.GetEventRecorderFor("class-controller"),
   369  		}).SetupWithManager(mgr); err != nil {
   370  			setupLog.Error(err, "unable to create controller", "controller", "Class")
   371  			os.Exit(1)
   372  		}
   373  
   374  		if err = (&appscontrollers.ServiceDescriptorReconciler{
   375  			Client: mgr.GetClient(),
   376  			Scheme: mgr.GetScheme(),
   377  		}).SetupWithManager(mgr); err != nil {
   378  			setupLog.Error(err, "unable to create controller", "controller", "ServiceDescriptor")
   379  			os.Exit(1)
   380  		}
   381  
   382  		if err = (&appscontrollers.BackupPolicyTemplateReconciler{
   383  			Client: mgr.GetClient(),
   384  			Scheme: mgr.GetScheme(),
   385  		}).SetupWithManager(mgr); err != nil {
   386  			setupLog.Error(err, "unable to create controller", "controller", "BackupPolicyTemplate")
   387  			os.Exit(1)
   388  		}
   389  	}
   390  
   391  	if viper.GetBool(extensionsFlagKey.viperName()) {
   392  		if err = (&extensionscontrollers.AddonReconciler{
   393  			Client:     mgr.GetClient(),
   394  			Scheme:     mgr.GetScheme(),
   395  			Recorder:   mgr.GetEventRecorderFor("addon-controller"),
   396  			RestConfig: mgr.GetConfig(),
   397  		}).SetupWithManager(mgr); err != nil {
   398  			setupLog.Error(err, "unable to create controller", "controller", "Addon")
   399  			os.Exit(1)
   400  		}
   401  	}
   402  
   403  	if viper.GetBool(workloadsFlagKey.viperName()) {
   404  		if err = (&workloadscontrollers.ReplicatedStateMachineReconciler{
   405  			Client:   mgr.GetClient(),
   406  			Scheme:   mgr.GetScheme(),
   407  			Recorder: mgr.GetEventRecorderFor("replicated-state-machine-controller"),
   408  		}).SetupWithManager(mgr); err != nil {
   409  			setupLog.Error(err, "unable to create controller", "controller", "ReplicatedStateMachine")
   410  			os.Exit(1)
   411  		}
   412  	}
   413  
   414  	if viper.GetBool(storageFlagKey.viperName()) {
   415  		if err = (&storagecontrollers.StorageProviderReconciler{
   416  			Client:   mgr.GetClient(),
   417  			Scheme:   mgr.GetScheme(),
   418  			Recorder: mgr.GetEventRecorderFor("storage-provider-controller"),
   419  		}).SetupWithManager(mgr); err != nil {
   420  			setupLog.Error(err, "unable to create controller", "controller", "StorageProvider")
   421  			os.Exit(1)
   422  		}
   423  	}
   424  	// +kubebuilder:scaffold:builder
   425  
   426  	if viper.GetBool("enable_webhooks") {
   427  
   428  		appsv1alpha1.RegisterWebhookManager(mgr)
   429  
   430  		if err = (&appsv1alpha1.Cluster{}).SetupWebhookWithManager(mgr); err != nil {
   431  			setupLog.Error(err, "unable to create webhook", "webhook", "Cluster")
   432  			os.Exit(1)
   433  		}
   434  
   435  		if err = (&appsv1alpha1.ClusterDefinition{}).SetupWebhookWithManager(mgr); err != nil {
   436  			setupLog.Error(err, "unable to create webhook", "webhook", "ClusterDefinition")
   437  			os.Exit(1)
   438  		}
   439  
   440  		if err = (&appsv1alpha1.ClusterVersion{}).SetupWebhookWithManager(mgr); err != nil {
   441  			setupLog.Error(err, "unable to create webhook", "webhook", "ClusterVersion")
   442  			os.Exit(1)
   443  		}
   444  
   445  		if err = (&appsv1alpha1.OpsRequest{}).SetupWebhookWithManager(mgr); err != nil {
   446  			setupLog.Error(err, "unable to create webhook", "webhook", "OpsRequest")
   447  			os.Exit(1)
   448  		}
   449  
   450  		if err = (&workloadsv1alpha1.ReplicatedStateMachine{}).SetupWebhookWithManager(mgr); err != nil {
   451  			setupLog.Error(err, "unable to create webhook", "webhook", "ReplicatedStateMachine")
   452  			os.Exit(1)
   453  		}
   454  
   455  		if err = (&appsv1alpha1.ServiceDescriptor{}).SetupWebhookWithManager(mgr); err != nil {
   456  			setupLog.Error(err, "unable to create webhook", "webhook", "ServiceDescriptor")
   457  			os.Exit(1)
   458  		}
   459  	}
   460  
   461  	if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
   462  		setupLog.Error(err, "unable to set up health check")
   463  		os.Exit(1)
   464  	}
   465  	if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
   466  		setupLog.Error(err, "unable to set up ready check")
   467  		os.Exit(1)
   468  	}
   469  
   470  	cli, err := discoverycli.NewDiscoveryClientForConfig(mgr.GetConfig())
   471  	if err != nil {
   472  		setupLog.Error(err, "unable to create discovery client")
   473  		os.Exit(1)
   474  	}
   475  
   476  	ver, err := cli.ServerVersion()
   477  	if err != nil {
   478  		setupLog.Error(err, "unable to discover version info")
   479  		os.Exit(1)
   480  	}
   481  	viper.SetDefault(constant.CfgKeyServerInfo, *ver)
   482  
   483  	setupLog.Info("starting manager")
   484  	if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
   485  		setupLog.Error(err, "problem running manager")
   486  		os.Exit(1)
   487  	}
   488  }