// Изменено: 2026-03-07 // main.go — точка входа. Запускает operator manager и REST API сервер параллельно. // Operator manager управляет Function/Trigger CRD через reconcile loop. // REST API (gorilla/mux) принимает запросы от Terraform provider. package main import ( "context" "flag" "fmt" "log/slog" "net/http" "os" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" slessv1alpha1 "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/api/v1alpha1" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/controllers" slessapi "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/api" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/api/handler" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/builder" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/config" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/postgres" "gitea-naeel.giteak8s.services.ngcloud.ru/naeel/sless/internal/storage/s3" //+kubebuilder:scaffold:imports ) var ( scheme = runtime.NewScheme() ) func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(slessv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } func main() { var metricsAddr string var enableLeaderElection bool var probeAddr string flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") flag.BoolVar(&enableLeaderElection, "leader-elect", false, "Enable leader election for controller manager. "+ "Enabling this will ensure there is only one active controller manager.") opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) flag.Parse() ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) // slog для REST API (JSON-формат для prod, text для dev) log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo})) // Загружаем конфиг из env vars cfg, err := config.Load() if err != nil { log.Error("load config", "err", err) os.Exit(1) } // Подключаемся к PostgreSQL pg, err := postgres.New(cfg.PostgresDSN) if err != nil { log.Error("connect postgres", "err", err) os.Exit(1) } defer pg.Close() // Применяем миграции БД — читаем SQL и передаём строкой migrationSQL, err := os.ReadFile("migrations/001_initial.sql") if err != nil { log.Error("read migration file", "err", err) os.Exit(1) } if err := pg.RunMigrations(context.Background(), string(migrationSQL)); err != nil { log.Error("run migrations", "err", err) os.Exit(1) } // S3 клиент s3Client, err := s3.New(cfg.S3Endpoint, cfg.S3AccessKey, cfg.S3SecretKey, cfg.S3Bucket, cfg.S3UseSSL) if err != nil { log.Error("connect s3", "err", err) os.Exit(1) } if err := s3Client.EnsureBucket(context.Background()); err != nil { log.Error("ensure s3 bucket", "err", err) os.Exit(1) } mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, MetricsBindAddress: metricsAddr, Port: 9443, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "4b6ba465.kube5s.ru", }) if err != nil { log.Error("unable to start manager", "err", err) os.Exit(1) } // Builder использует k8s client из manager'а bldr := builder.New(mgr.GetClient(), builder.Config{ BuilderImage: cfg.BuilderImage, RegistryHost: cfg.RegistryHost, RegistrySecret: cfg.RegistrySecret, S3Endpoint: cfg.S3Endpoint, S3AccessKey: cfg.S3AccessKey, S3SecretKey: cfg.S3SecretKey, S3Bucket: cfg.S3Bucket, Namespace: "sless", }) if err = (&controllers.FunctionReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Builder: bldr, RegistrySecret: cfg.RegistrySecret, OperatorNamespace: "sless", }).SetupWithManager(mgr); err != nil { log.Error("unable to create controller", "controller", "Function", "err", err) os.Exit(1) } if err = (&controllers.TriggerReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), IngressHost: cfg.IngressHost, ExternalURL: cfg.ExternalURL, }).SetupWithManager(mgr); err != nil { log.Error("unable to create controller", "controller", "Trigger", "err", err) os.Exit(1) } if err = (&controllers.FunctionJobReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), RegistrySecret: cfg.RegistrySecret, KubeClient: kubernetes.NewForConfigOrDie(mgr.GetConfig()), }).SetupWithManager(mgr); err != nil { log.Error("unable to create controller", "controller", "FunctionJob", "err", err) os.Exit(1) } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { log.Error("unable to set up health check", "err", err) os.Exit(1) } if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { log.Error("unable to set up ready check", "err", err) os.Exit(1) } // REST API сервер — запускается параллельно с operator manager apiHandler := slessapi.NewRouter(&handler.Handler{ K8s: mgr.GetClient(), Scheme: mgr.GetScheme(), S3: s3Client, PG: pg, Log: log, }, log) go func() { addr := fmt.Sprintf(":%d", cfg.APIPort) log.Info("starting REST API", "addr", addr) if err := http.ListenAndServe(addr, apiHandler); err != nil { log.Error("REST API server failed", "err", err) os.Exit(1) } }() log.Info("starting operator manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { log.Error("problem running manager", "err", err) os.Exit(1) } }