github.com/verrazzano/verrazzano@v1.7.0/platform-operator/internal/operatorinit/run_operator.go (about) 1 // Copyright (c) 2022, 2023, Oracle and/or its affiliates. 2 // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. 3 4 package operatorinit 5 6 import ( 7 "context" 8 "github.com/pkg/errors" 9 moduleapi "github.com/verrazzano/verrazzano-modules/module-operator/apis/platform/v1alpha1" 10 "github.com/verrazzano/verrazzano-modules/module-operator/controllers/module" 11 "github.com/verrazzano/verrazzano/pkg/k8sutil" 12 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 13 "github.com/verrazzano/verrazzano/pkg/nginxutil" 14 "github.com/verrazzano/verrazzano/platform-operator/constants" 15 "github.com/verrazzano/verrazzano/platform-operator/controllers/configmaps/components" 16 "github.com/verrazzano/verrazzano/platform-operator/controllers/configmaps/overrides" 17 opensearchcontroller "github.com/verrazzano/verrazzano/platform-operator/controllers/integration/opensearch" 18 modulehandlerfactory "github.com/verrazzano/verrazzano/platform-operator/controllers/module/component-handler/factory" 19 "github.com/verrazzano/verrazzano/platform-operator/controllers/secrets" 20 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/component/registry" 21 verrazzancontroller "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/controller" 22 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/healthcheck" 23 "github.com/verrazzano/verrazzano/platform-operator/controllers/verrazzano/mysqlcheck" 24 "github.com/verrazzano/verrazzano/platform-operator/namespacewatch" 25 "sigs.k8s.io/controller-runtime/pkg/controller" 26 27 "github.com/verrazzano/verrazzano/platform-operator/internal/config" 28 "github.com/verrazzano/verrazzano/platform-operator/metricsexporter" 29 "go.uber.org/zap" 30 corev1 "k8s.io/api/core/v1" 31 k8serrors "k8s.io/apimachinery/pkg/api/errors" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 "k8s.io/client-go/kubernetes" 35 "os" 36 controllerruntime "sigs.k8s.io/controller-runtime" 37 "strings" 38 "time" 39 ) 40 41 const vpoHelmChartConfigMapName = "vpo-helm-chart" 42 43 // StartPlatformOperator Platform operator execution entry point 44 func StartPlatformOperator(vzconfig config.OperatorConfig, log *zap.SugaredLogger, scheme *runtime.Scheme) error { 45 // Determine NGINX namespace before initializing components 46 ingressNGINXNamespace, err := nginxutil.DetermineNamespaceForIngressNGINX(vzlog.DefaultLogger()) 47 if err != nil { 48 return err 49 } 50 nginxutil.SetIngressNGINXNamespace(ingressNGINXNamespace) 51 52 if err := CreateVerrazzanoVersionsConfigMap(context.Background()); err != nil { 53 return err 54 } 55 56 registry.InitRegistry() 57 metricsexporter.Init() 58 59 chartDir := config.GetHelmVPOChartsDir() 60 files, err := os.ReadDir(chartDir) 61 if err != nil { 62 return errors.Wrap(err, "Failed to read the verrazzano-platform-operator helm chart directory") 63 } 64 vpoHelmChartConfigMap := &corev1.ConfigMap{ 65 ObjectMeta: metav1.ObjectMeta{ 66 Name: vpoHelmChartConfigMapName, 67 Namespace: constants.VerrazzanoInstallNamespace, 68 }, 69 } 70 err = generateConfigMapFromHelmChartFiles(chartDir, "", files, vpoHelmChartConfigMap) 71 if err != nil { 72 return errors.Wrap(err, "Failed to generate config map containing the verrazzano-platform-operator helm chart") 73 } 74 kubeClient, err := k8sutil.GetKubernetesClientset() 75 if err != nil { 76 return err 77 } 78 err = createVPOHelmChartConfigMap(kubeClient, vpoHelmChartConfigMap) 79 if err != nil { 80 return errors.Wrap(err, "Failed to create/update config map containing the verrazzano-platform-operator helm chart") 81 } 82 83 mgr, err := controllerruntime.NewManager(k8sutil.GetConfigOrDieFromController(), controllerruntime.Options{ 84 Scheme: scheme, 85 MetricsBindAddress: vzconfig.MetricsAddr, 86 Port: 8080, 87 LeaderElection: vzconfig.LeaderElectionEnabled, 88 LeaderElectionID: "3ec4d290.verrazzano.io", 89 }) 90 if err != nil { 91 return errors.Wrap(err, "Failed to create a controller-runtime manager") 92 } 93 94 metricsexporter.StartMetricsServer(log) 95 96 // Set up the reconciler 97 statusUpdater := healthcheck.NewStatusUpdater(mgr.GetClient()) 98 99 // Init the module controllers 100 if err := initModuleControllers(log, mgr); err != nil { 101 log.Errorf("Failed to start all module controllers", err) 102 return errors.Wrap(err, "Failed to initialize modules controllers for the components") 103 } 104 105 // init verrazzano controller 106 if err := verrazzancontroller.InitController(mgr); err != nil { 107 log.Errorf("Failed to start module-based Verrazzano controller", err) 108 return errors.Wrap(err, "Failed to initialize controller for module-based Verrazzano controller") 109 } 110 111 if err := opensearchcontroller.InitController(mgr); err != nil { 112 log.Errorf("Failed to start module-based opensearch controller", err) 113 return errors.Wrap(err, "Failed to initialize controller for module-based opensearch controller") 114 } 115 116 // Setup health checker 117 healthCheck := healthcheck.NewHealthChecker(statusUpdater, mgr.GetClient(), time.Duration(vzconfig.HealthCheckPeriodSeconds)*time.Second) 118 if vzconfig.HealthCheckPeriodSeconds > 0 { 119 healthCheck.Start() 120 } 121 122 dynamicClient, err := k8sutil.GetDynamicClient() 123 if err != nil { 124 return errors.Wrapf(err, "Failed to get Dynamic Client") 125 } 126 // Setup secrets reconciler 127 if err = (&secrets.VerrazzanoSecretsReconciler{ 128 Client: mgr.GetClient(), 129 DynamicClient: dynamicClient, 130 Scheme: mgr.GetScheme(), 131 StatusUpdater: statusUpdater, 132 }).SetupWithManager(mgr); err != nil { 133 return errors.Wrapf(err, "Failed to setup controller VerrazzanoSecrets") 134 } 135 136 // Setup configMaps reconciler 137 if err = (&overrides.OverridesConfigMapsReconciler{ 138 Client: mgr.GetClient(), 139 Scheme: mgr.GetScheme(), 140 StatusUpdater: statusUpdater, 141 }).SetupWithManager(mgr); err != nil { 142 return errors.Wrap(err, "Failed to setup controller VerrazzanoConfigMaps") 143 } 144 145 // Setup MySQL checker 146 mysqlCheck, err := mysqlcheck.NewMySQLChecker(mgr.GetClient(), time.Duration(vzconfig.MySQLCheckPeriodSeconds)*time.Second, time.Duration(vzconfig.MySQLRepairTimeoutSeconds)*time.Second) 147 if err != nil { 148 return errors.Wrap(err, "Failed starting MySQLChecker") 149 } 150 mysqlCheck.Start() 151 152 // Setup Namespaces watcher 153 watchNamespace := namespacewatch.NewNamespaceWatcher(mgr.GetClient(), time.Duration(vzconfig.NamespacePeriodSeconds)*time.Second) 154 if vzconfig.NamespacePeriodSeconds > 0 { 155 watchNamespace.Start() 156 } 157 158 // Setup stacks reconciler 159 if err = (&components.ComponentConfigMapReconciler{ 160 Client: mgr.GetClient(), 161 Scheme: mgr.GetScheme(), 162 DryRun: vzconfig.DryRun, 163 }).SetupWithManager(mgr); err != nil { 164 return errors.Wrap(err, "Failed to setup controller for Verrazzano Stacks") 165 } 166 167 // +kubebuilder:scaffold:builder 168 log.Info("Starting controller-runtime manager") 169 if err := mgr.Start(controllerruntime.SetupSignalHandler()); err != nil { 170 return errors.Wrap(err, "Failed starting controller-runtime manager: %v") 171 } 172 173 return nil 174 } 175 176 // generateConfigMapFromHelmChartFiles generates a config map with the files from a given helm chart directory 177 func generateConfigMapFromHelmChartFiles(dir string, key string, files []os.DirEntry, configMap *corev1.ConfigMap) error { 178 for _, file := range files { 179 newKey := file.Name() 180 if len(key) != 0 { 181 newKey = key + "/" + file.Name() 182 } 183 if file.IsDir() { 184 files2, err := os.ReadDir(dir + "/" + newKey) 185 if err != nil { 186 return err 187 } 188 err = generateConfigMapFromHelmChartFiles(dir, newKey, files2, configMap) 189 if err != nil { 190 return err 191 } 192 } else { 193 err := addKeyForFileToConfigMap(dir, newKey, configMap) 194 if err != nil { 195 return nil 196 } 197 } 198 } 199 200 return nil 201 } 202 203 // addKeyForFileToConfigMap adds a key for a file to a config map 204 func addKeyForFileToConfigMap(dir string, key string, configMap *corev1.ConfigMap) error { 205 data, err := os.ReadFile(dir + "/" + key) 206 if err != nil { 207 return err 208 } 209 if configMap.Data == nil { 210 configMap.Data = make(map[string]string) 211 } 212 // Use "..." as a path separator since "/" is an invalid character for a config map data key 213 keyName := strings.ReplaceAll(key, "/", "...") 214 configMap.Data[keyName] = string(data) 215 216 return nil 217 } 218 219 // createVPOHelmChartConfigMap creates/updates a config map containing the VPO helm chart 220 func createVPOHelmChartConfigMap(kubeClient kubernetes.Interface, configMap *corev1.ConfigMap) error { 221 _, err := kubeClient.CoreV1().ConfigMaps(constants.VerrazzanoInstallNamespace).Get(context.TODO(), configMap.Name, metav1.GetOptions{}) 222 if err != nil { 223 if k8serrors.IsNotFound(err) { 224 _, err = kubeClient.CoreV1().ConfigMaps(constants.VerrazzanoInstallNamespace).Create(context.TODO(), configMap, metav1.CreateOptions{}) 225 } 226 } else { 227 _, err = kubeClient.CoreV1().ConfigMaps(constants.VerrazzanoInstallNamespace).Update(context.TODO(), configMap, metav1.UpdateOptions{}) 228 } 229 230 return err 231 } 232 233 // initModuleControllers creates a controller for every module 234 // The controller uses the module name (i.e. component name) as a predicate, 235 // so that each controller only processes Module CRs for the respective component. 236 func initModuleControllers(log *zap.SugaredLogger, mgr controllerruntime.Manager) error { 237 // Run 50 controllers max concurrently. Even though there is a controller for each 238 // module, the controller-runtime serializes reconciles if the MaxConcurrentReconciles is 1. 239 // This is because it is the same resource type being reconcile, but with a 240 // different predicate per controller. Also, with the MaxConcurrentReconciles > 1, there 241 // is a chance that the controller-runtime will call the same controller (e.g. istio) to 242 // reconcile, while it is already reconciling a given CR. There is a check in the 243 // module-operator package to catch this and requeue, thereby never allowing more 244 // than 1 instance of the same module from having more than 1 outstanding reconcile happening. 245 // See https://github.com/verrazzano/verrazzano-modules/blob/main/module-operator/controllers/module/reconciler.go#L225 246 // for details. 247 const maxReconciles = 50 248 249 // Temp hack to prevent module controller from looking up helm info 250 module.IgnoreHelmInfo() 251 252 for _, comp := range registry.GetComponents() { 253 // Create and initialize the module controller. Note that the implementation of the module controller 254 // is in the module-operator package (in the module-operator repo). This is the exact 255 // same controller that is used by the module-operator used by OCNE. The only difference 256 // is the set of handlers. This code uses component shim handlers in the VPO code, whereas 257 // the OCNE module-operator uses OCNE handlers in the module-operator controller. See 258 // ModuleHandlerInfo param below. Also see the module-operator where the OCNE handlers are used 259 // for comparison: https://github.com/verrazzano/verrazzano-modules/blob/main/module-operator/main.go 260 if err := module.InitController(module.ModuleControllerConfig{ 261 ControllerManager: mgr, 262 ModuleHandlerInfo: modulehandlerfactory.NewModuleHandlerInfo(), 263 ModuleClass: moduleapi.ModuleClassType(comp.Name()), 264 WatchDescriptors: comp.GetWatchDescriptors(), 265 ControllerOptions: &controller.Options{MaxConcurrentReconciles: maxReconciles}, 266 }); err != nil { 267 log.Errorf("Failed to start the controller for module %s:%v", comp.Name(), err) 268 return err 269 } 270 } 271 return nil 272 }