sqs-operator v0.1.6: SH02/SH03/SH04 fix + test_v2_suite + v2 test results

- v0.1.5: ensureHealthy checks all 4 resources (Deployment/Service/ConfigMap/Ingress)
  Service/ConfigMap/Ingress now auto-recreate in 2-4s when manually deleted
- v0.1.6: revert MT03 configuration-snippet (nginx blocks risky annotations by default)
  Tenant isolation deferred to Keycloak JWT in production
- test_v2_suite.sh: 8 phases, 52 tests, timing/resources/concurrent/30min marathon
- Results: 40 PASS / 4 FAIL / 7 WARN (known: OOM restart under burst load)
  SH recovery: Deployment=48s Service=2s ConfigMap=4s Ingress=2s All3=2s
  Marathon: 11815 iter, 10830 sent, 2 pod restarts (OOM under load)
This commit is contained in:
Naeel
2026-04-07 19:14:00 +03:00
parent 18e57cadc7
commit c132c68d74
3 changed files with 1446 additions and 12 deletions
@@ -202,21 +202,38 @@ func (r *QueueServiceReconciler) checkReady(ctx context.Context, qs *sqsv1alpha1
}
// ensureHealthy мониторит состояние готового инстанса (фаза Ready).
// Проверяет наличие всех критических ресурсов (Deployment, Service, ConfigMap, Ingress).
// Если любой ресурс пропал — переход в Pending для пересоздания (OwnerReference не работает cross-namespace).
// Если pod упал — переходим в Failed для последующего восстановления.
func (r *QueueServiceReconciler) ensureHealthy(ctx context.Context, qs *sqsv1alpha1.QueueService, tenantNS string) (ctrl.Result, error) {
deployName := "sqs-" + qs.Spec.TenantID
tenantID := qs.Spec.TenantID
deploy := &appsv1.Deployment{}
if err := r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: deployName}, deploy); err != nil {
if errors.IsNotFound(err) {
qs.Status.Phase = sqsv1alpha1.QueueServicePhasePending
qs.Status.Message = "deployment disappeared, reprovisioning"
_ = r.Status().Update(ctx, qs)
return ctrl.Result{Requeue: true}, nil
// Проверяем критические ресурсы — если удалены вручную, пересоздаём через provision.
checkResources := []struct {
name string
obj client.Object
}{
{"sqs-" + tenantID, &appsv1.Deployment{}},
{"sqs-svc-" + tenantID, &corev1.Service{}},
{"sqs-cfg-" + tenantID, &corev1.ConfigMap{}},
{"sqs-ing-" + tenantID, &netv1.Ingress{}},
}
for _, res := range checkResources {
if err := r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: res.name}, res.obj); err != nil {
if errors.IsNotFound(err) {
log.FromContext(ctx).Info("resource disappeared, reprovisioning", "resource", res.name)
qs.Status.Phase = sqsv1alpha1.QueueServicePhasePending
qs.Status.Message = fmt.Sprintf("%s disappeared, reprovisioning", res.name)
_ = r.Status().Update(ctx, qs)
return ctrl.Result{Requeue: true}, nil
}
return ctrl.Result{}, fmt.Errorf("health check get %s: %w", res.name, err)
}
return ctrl.Result{}, fmt.Errorf("health check get deployment: %w", err)
}
// Deployment существует (проверен в цикле выше) — проверяем готовность pod
deploy := &appsv1.Deployment{}
_ = r.Get(ctx, client.ObjectKey{Namespace: tenantNS, Name: "sqs-" + tenantID}, deploy)
if deploy.Status.AvailableReplicas < 1 {
qs.Status.Phase = sqsv1alpha1.QueueServicePhaseFailed
qs.Status.Message = "pod unavailable"
@@ -224,8 +241,8 @@ func (r *QueueServiceReconciler) ensureHealthy(ctx context.Context, qs *sqsv1alp
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
// Всё хорошо — следующий check через 30 секунд
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
// Всё хорошо — следующий check через 15 секунд
return ctrl.Result{RequeueAfter: 15 * time.Second}, nil
}
// recoverFromFailed пробует восстановиться из Failed состояния.
@@ -608,6 +625,9 @@ func (r *QueueServiceReconciler) ensureIngress(ctx context.Context, qs *sqsv1alp
pathType := netv1.PathTypePrefix
svcPort := int32(elasticMQPort)
// MT03 (auth/isolation): configuration-snippet НЕ используется — nginx Ingress Controller
// блокирует его как "risky annotation" (CVE-2021-25742 mitigation, включён по умолчанию с v1.9+).
// Изоляция тенантов будет обеспечена через Keycloak JWT в проде (не через SigV4/snippet).
ing = &netv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: name,
@@ -615,7 +635,6 @@ func (r *QueueServiceReconciler) ensureIngress(ctx context.Context, qs *sqsv1alp
Labels: sqsLabels(qs.Spec.TenantID),
Annotations: map[string]string{
// Без rewrite-target: ElasticMQ JVM слушает по полному пути /sqs/{tenantId}/...
// (context-path в конфиге определяет listen path у JVM образа, в отличие от native).
"nginx.ingress.kubernetes.io/proxy-read-timeout": "60",
"nginx.ingress.kubernetes.io/proxy-send-timeout": "60",
"nginx.ingress.kubernetes.io/proxy-body-size": "10m",