Files
sless/main.go
T
“Naeel” 2ee9cae6d2 feat: proxy /fn/{namespace}/{name} — обход wildcard DNS
Проблема: wildcard DNS *.fn.kube5s.ru недоступен.
Решение: прокси через sless-api.kube5s.ru/fn/{ns}/{name}.

- handler/invoke.go: прокси к Service функции внутри кластера
- router.go: /fn/ без auth токена, /v1/ с auth (gorilla Use())
- config.go: поле ExternalURL (EXTERNAL_URL env)
- trigger_controller.go: если ExternalURL задан — URL = ExternalURL/fn/{ns}/{fn}
  иначе fallback: Ingress + поддомен (прежнее поведение)
- operator.yaml: EXTERNAL_URL=https://sless-api.kube5s.ru, image v0.1.5

Оператор v0.1.5 задеплоен.
E2E: curl https://sless-api.kube5s.ru/fn/default/hello-node → {"message":"Hello, Naeel! (nodejs20)"}
2026-03-07 18:36:03 +04:00

191 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Изменено: 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"
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,
}).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,
}).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,
}, cfg.APIToken, 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)
}
}