diff --git a/controllers/functionjob_controller.go b/controllers/functionjob_controller.go index 320fa4c..5035b49 100644 --- a/controllers/functionjob_controller.go +++ b/controllers/functionjob_controller.go @@ -1,4 +1,4 @@ -// Изменено: 2026-03-08 +// Изменено: 2026-03-08 (fix: RequeueAfter для poll статуса Job) // FunctionJobReconciler — контроллер одноразовых запусков функций. // При создании FunctionJob: // 1. Ждёт пока Function станет Ready @@ -14,6 +14,7 @@ package controllers import ( "context" "fmt" + "time" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -180,6 +181,8 @@ func (r *FunctionJobReconciler) Reconcile(ctx context.Context, req ctrl.Request) } // syncJobStatus читает статус k8s Job и обновляет FunctionJob.Status. +// Если Job ещё выполняется — запрашивает повторный reconcile через 5 сек +// (Owns не работает кросс-неймспейсно, поэтому используем polling). func (r *FunctionJobReconciler) syncJobStatus(ctx context.Context, fj *slessv1alpha1.FunctionJob, job *batchv1.Job) (ctrl.Result, error) { if job.Status.Succeeded > 0 { now := metav1.Now() @@ -191,8 +194,13 @@ func (r *FunctionJobReconciler) syncJobStatus(ctx context.Context, fj *slessv1al fj.Status.Phase = slessv1alpha1.FunctionJobPhaseFailed fj.Status.CompletionTime = &now fj.Status.Message = "job failed, check pod logs: kubectl logs -n sless-fn-" + fj.Namespace + " -l functionjob=" + fj.Name + } else { + // Job ещё выполняется — перечитаем через 5 секунд + if err := r.Status().Update(ctx, fj); err != nil { + return ctrl.Result{}, fmt.Errorf("sync job status: %w", err) + } + return ctrl.Result{RequeueAfter: 5 * time.Second}, nil } - // Running — ничего не меняем, перечитаем при следующем reconcile if err := r.Status().Update(ctx, fj); err != nil { return ctrl.Result{}, fmt.Errorf("sync job status: %w", err) } @@ -251,10 +259,12 @@ func fnEnvVars(fn *slessv1alpha1.Function) []corev1.EnvVar { func int32Ptr(i int32) *int32 { return &i } -// SetupWithManager регистрирует контроллер и настраивает watch на k8s Job. +// SetupWithManager регистрирует контроллер. +// Owns(&batchv1.Job{}) намеренно убрано: Job создаётся в другом namespace +// (sless-fn-*), где OwnerReference кросс-неймспейсно не работают. +// Вместо этого используется RequeueAfter-polling в syncJobStatus. func (r *FunctionJobReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&slessv1alpha1.FunctionJob{}). - Owns(&batchv1.Job{}). Complete(r) } diff --git a/deployments/k8s/operator.yaml b/deployments/k8s/operator.yaml index 59e3359..eb93da2 100644 --- a/deployments/k8s/operator.yaml +++ b/deployments/k8s/operator.yaml @@ -70,7 +70,7 @@ spec: containers: - name: operator # При обновлении версии оператора — менять тег здесь (не latest!) - image: naeel/sless-operator:v0.1.8 + image: naeel/sless-operator:v0.1.10 # Always — чтобы всегда тянуть по точному тегу (не кешировать старый) imagePullPolicy: Always ports: diff --git a/deployments/k8s/rbac.yaml b/deployments/k8s/rbac.yaml index d544522..f87075e 100644 --- a/deployments/k8s/rbac.yaml +++ b/deployments/k8s/rbac.yaml @@ -38,9 +38,10 @@ rules: resources: ["services", "namespaces"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] # Secrets — pull-секрет копируется в sless-fn-* namespace при создании функций + # list/watch нужны для controller-runtime cache (информер на secrets) - apiGroups: [""] resources: ["secrets"] - verbs: ["get", "create"] + verbs: ["get", "list", "watch", "create", "update", "patch"] - apiGroups: ["networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] diff --git a/examples/hello-node/code/handler-http.js b/examples/hello-node/code/handler-http.js index 7a15d05..ec016dc 100644 --- a/examples/hello-node/code/handler-http.js +++ b/examples/hello-node/code/handler-http.js @@ -5,3 +5,4 @@ exports.handle = async (event) => { const name = event.name || 'World'; return { message: `Hello, ${name}!` }; }; + diff --git a/examples/hello-node/code/handler-job.js b/examples/hello-node/code/handler-job.js index 3342114..5081ff9 100644 --- a/examples/hello-node/code/handler-job.js +++ b/examples/hello-node/code/handler-job.js @@ -7,3 +7,4 @@ exports.handle = async (event) => { const sum = numbers.reduce((acc, n) => acc + n, 0); return { input: numbers, sum, count: numbers.length }; }; + diff --git a/examples/hello-node/handler-http.zip b/examples/hello-node/handler-http.zip index c0c865e..03b9353 100644 Binary files a/examples/hello-node/handler-http.zip and b/examples/hello-node/handler-http.zip differ diff --git a/examples/hello-node/handler-job.zip b/examples/hello-node/handler-job.zip index 283b2cf..a88be32 100644 Binary files a/examples/hello-node/handler-job.zip and b/examples/hello-node/handler-job.zip differ diff --git a/internal/api/handler/upload.go b/internal/api/handler/upload.go index 900c2c6..acfdefa 100644 --- a/internal/api/handler/upload.go +++ b/internal/api/handler/upload.go @@ -33,7 +33,7 @@ func runtimeBaseImage(runtime string) (string, error) { case "python3.11": return "naeel/sless-runtime-python3.11:v0.1.0", nil case "nodejs20": - return "naeel/sless-runtime-nodejs20:v0.1.0", nil + return "naeel/sless-runtime-nodejs20:v0.1.1", nil default: return "", fmt.Errorf("unsupported runtime: %q (supported: python3.11, nodejs20)", runtime) }