sqs-operator v0.1.8: fix UI assets ingress (/_next/ path-based routing)

- ensureIngressUIAssets: Prefix ingress /_next/ без rewrite -> UI port 3000
- Next.js basePath="" захардкожен — static assets идут без тенантного префикса
- Решение через отдельный ingress объект (один на тенант, контент идентичен)
This commit is contained in:
Naeel
2026-04-08 19:40:42 +03:00
parent 64626659a9
commit 657fc33119
@@ -165,6 +165,11 @@ func (r *QueueServiceReconciler) provision(ctx context.Context, qs *sqsv1alpha1.
if err := r.ensureIngressUI(ctx, qs, tenantNS, uiIngName, svcName); err != nil { if err := r.ensureIngressUI(ctx, qs, tenantNS, uiIngName, svcName); err != nil {
return r.setFailed(ctx, qs, fmt.Sprintf("ensure ingress-ui: %v", err)) 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 // Все ресурсы созданы — переходим в Provisioning, ждём готовности pod
@@ -236,6 +241,10 @@ func (r *QueueServiceReconciler) ensureHealthy(ctx context.Context, qs *sqsv1alp
name string name string
obj client.Object obj client.Object
}{"sqs-ing-ui-" + tenantID, &netv1.Ingress{}}) }{"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 { for _, res := range checkResources {
if err := r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: res.name}, res.obj); err != nil { 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)} 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 (если был создан) // Удаляем Ingress UI (если был создан)
uiIng := &netv1.Ingress{} uiIng := &netv1.Ingress{}
if err := r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: "sqs-ing-ui-" + tenantID}, uiIng); err == nil { 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) 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)
}