From 657fc331199fc695f5961bb7db2e2f6bcdf7e94e Mon Sep 17 00:00:00 2001 From: Naeel Date: Wed, 8 Apr 2026 19:40:42 +0300 Subject: [PATCH] sqs-operator v0.1.8: fix UI assets ingress (/_next/ path-based routing) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ensureIngressUIAssets: Prefix ingress /_next/ без rewrite -> UI port 3000 - Next.js basePath="" захардкожен — static assets идут без тенантного префикса - Решение через отдельный ingress объект (один на тенант, контент идентичен) --- .../controller/queueservice_controller.go | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/sqs-operator/internal/controller/queueservice_controller.go b/sqs-operator/internal/controller/queueservice_controller.go index 12a002d..3039f10 100644 --- a/sqs-operator/internal/controller/queueservice_controller.go +++ b/sqs-operator/internal/controller/queueservice_controller.go @@ -165,6 +165,11 @@ func (r *QueueServiceReconciler) provision(ctx context.Context, qs *sqsv1alpha1. if err := r.ensureIngressUI(ctx, qs, tenantNS, uiIngName, svcName); err != nil { return r.setFailed(ctx, qs, fmt.Sprintf("ensure ingress-ui: %v", err)) } + // Отдельный ingress для /_next/ ресурсов Next.js (без rewrite — basePath="" в образе). + assetsIngName := "sqs-ing-ui-assets-" + tenantID + if err := r.ensureIngressUIAssets(ctx, qs, tenantNS, assetsIngName, svcName); err != nil { + return r.setFailed(ctx, qs, fmt.Sprintf("ensure ingress-ui-assets: %v", err)) + } } // Все ресурсы созданы — переходим в Provisioning, ждём готовности pod @@ -236,6 +241,10 @@ func (r *QueueServiceReconciler) ensureHealthy(ctx context.Context, qs *sqsv1alp name string obj client.Object }{"sqs-ing-ui-" + tenantID, &netv1.Ingress{}}) + checkResources = append(checkResources, struct { + name string + obj client.Object + }{"sqs-ing-ui-assets-" + tenantID, &netv1.Ingress{}}) } for _, res := range checkResources { if err := r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: res.name}, res.obj); err != nil { @@ -302,6 +311,12 @@ func (r *QueueServiceReconciler) handleDeletion(ctx context.Context, qs *sqsv1al deleteOpts := []client.DeleteOption{client.PropagationPolicy(metav1.DeletePropagationForeground)} + // Удаляем Ingress UI assets (/_next/) если был создан + uiAssetsIng := &netv1.Ingress{} + if err := r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: "sqs-ing-ui-assets-" + tenantID}, uiAssetsIng); err == nil { + _ = r.Delete(ctx, uiAssetsIng, deleteOpts...) + } + // Удаляем Ingress UI (если был создан) uiIng := &netv1.Ingress{} if err := r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: "sqs-ing-ui-" + tenantID}, uiIng); err == nil { @@ -833,3 +848,64 @@ func (r *QueueServiceReconciler) ensureIngressUI(ctx context.Context, qs *sqsv1a } return r.Create(ctx, ing) } + +// ensureIngressUIAssets создаёт Ingress для статических ресурсов Next.js (/_next/). +// Next.js собран с basePath="" — ресурсы запрашиваются по абсолютному пути /_next/... +// Без rewrite: nginx передаёт /_next/... напрямую в UI-контейнер (port 3000). +// Все UI-поды одного образа отдают идентичные /_next/ файлы — конфликтов нет. +func (r *QueueServiceReconciler) ensureIngressUIAssets(ctx context.Context, qs *sqsv1alpha1.QueueService, ns, name, svcName string) error { + ing := &netv1.Ingress{} + if err := r.Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, ing); err == nil { + return nil + } else if !errors.IsNotFound(err) { + return fmt.Errorf("get ui-assets ingress: %w", err) + } + + // Prefix без rewrite-target — nginx пробрасывает /_next/... как есть в Next.js сервер. + pathType := netv1.PathTypePrefix + + ing = &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + Labels: sqsLabels(qs.Spec.TenantID), + Annotations: map[string]string{ + "nginx.ingress.kubernetes.io/proxy-read-timeout": "30", + "cert-manager.io/cluster-issuer": "letsencrypt-prod", + }, + }, + Spec: netv1.IngressSpec{ + IngressClassName: strPtr("nginx"), + TLS: []netv1.IngressTLS{ + { + Hosts: []string{r.SQSExternalHost}, + SecretName: "sqs-tls-" + qs.Spec.TenantID, + }, + }, + Rules: []netv1.IngressRule{ + { + Host: r.SQSExternalHost, + IngressRuleValue: netv1.IngressRuleValue{ + HTTP: &netv1.HTTPIngressRuleValue{ + Paths: []netv1.HTTPIngressPath{ + { + Path: "/_next/", + PathType: &pathType, + Backend: netv1.IngressBackend{ + Service: &netv1.IngressServiceBackend{ + Name: svcName, + Port: netv1.ServiceBackendPort{ + Number: int32(elasticMQUIPort), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + return r.Create(ctx, ing) +}