DockerHub (flat): REGISTRY_HOST=naeel, REGISTRY_PROJECT=<empty>
-> naeel/{nsPrefix}-{func}:{tag}
Harbor/GCR (project): REGISTRY_HOST=host, REGISTRY_PROJECT=proj
-> host/proj/{func}:{tag}
Switch registry by changing 2 env vars only.
227 lines
7.7 KiB
Go
227 lines
7.7 KiB
Go
// Изменено: 2026-03-20 (function-service-split: добавлена регистрация ServiceReconciler)
|
||
// 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/harbor"
|
||
"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'а
|
||
// harborClient — nil если HARBOR_USER/HARBOR_PASS не заданы (EnsureProject пропускается).
|
||
var harborClient *harbor.Client
|
||
var harborProjecter builder.Projecter
|
||
if cfg.HarborUser != "" && cfg.HarborPass != "" {
|
||
harborClient = harbor.New(cfg.RegistryHost, cfg.HarborUser, cfg.HarborPass)
|
||
harborProjecter = harborClient
|
||
log.Info("harbor client configured", "host", cfg.RegistryHost)
|
||
} else {
|
||
log.Info("harbor client disabled (HARBOR_USER/HARBOR_PASS not set)")
|
||
}
|
||
|
||
bldr := builder.New(mgr.GetClient(), builder.Config{
|
||
BuilderImage: cfg.BuilderImage,
|
||
RegistryHost: cfg.RegistryHost,
|
||
RegistryProject: cfg.RegistryProject,
|
||
RegistrySecret: cfg.RegistrySecret,
|
||
S3Endpoint: cfg.S3Endpoint,
|
||
S3AccessKey: cfg.S3AccessKey,
|
||
S3SecretKey: cfg.S3SecretKey,
|
||
S3Bucket: cfg.S3Bucket,
|
||
Namespace: "sless",
|
||
HarborClient: harborProjecter,
|
||
})
|
||
|
||
if err = (&controllers.FunctionReconciler{
|
||
Client: mgr.GetClient(),
|
||
Scheme: mgr.GetScheme(),
|
||
Builder: bldr,
|
||
KubeClient: kubernetes.NewForConfigOrDie(mgr.GetConfig()),
|
||
RegistrySecret: cfg.RegistrySecret,
|
||
OperatorNamespace: "sless",
|
||
HarborClient: harborClient,
|
||
}).SetupWithManager(mgr); err != nil {
|
||
log.Error("unable to create controller", "controller", "Function", "err", err)
|
||
os.Exit(1)
|
||
}
|
||
if err = (&controllers.ServiceReconciler{
|
||
Client: mgr.GetClient(),
|
||
Scheme: mgr.GetScheme(),
|
||
Builder: bldr,
|
||
KubeClient: kubernetes.NewForConfigOrDie(mgr.GetConfig()),
|
||
RegistrySecret: cfg.RegistrySecret,
|
||
OperatorNamespace: "sless",
|
||
HarborClient: harborClient,
|
||
ExternalURL: cfg.ExternalURL,
|
||
IngressHost: cfg.IngressHost,
|
||
}).SetupWithManager(mgr); err != nil {
|
||
log.Error("unable to create controller", "controller", "Service", "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()),
|
||
Builder: bldr,
|
||
OperatorNamespace: "sless",
|
||
}).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)
|
||
}
|
||
}
|