feat: sqs-operator — CRD QueueService, контроллер, ElasticMQ per-tenant

This commit is contained in:
Naeel
2026-04-07 13:56:53 +03:00
parent a22a37bef3
commit cbe61d9f62
9 changed files with 1956 additions and 0 deletions
@@ -0,0 +1,181 @@
// Copyright 2026 SoftwareLess
// Licensed under the Apache License, Version 2.0
// Создан: 2026-04-07
// queueservice_types.go — CRD QueueService: managed SQS-совместимый инстанс очередей.
// Каждый тенант облачного провайдера создаёт один QueueService и получает изолированный
// ElasticMQ pod с SQS-совместимым API. Клиент работает через стандартный AWS SDK,
// меняя только endpoint на https://sqs.kube5s.ru/sqs/{tenantId}.
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
// GroupVersion — API group и версия для QueueService CRD.
var (
GroupVersion = schema.GroupVersion{Group: "sqs.kube5s.ru", Version: "v1alpha1"}
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
AddToScheme = SchemeBuilder.AddToScheme
)
// QueueServiceSpec — желаемое состояние инстанса очередей тенанта.
type QueueServiceSpec struct {
// TenantID — уникальный ID тенанта облачного провайдера.
// Используется как имя namespace (sless-fn-{tenantId}), имена ресурсов и в endpoint URL.
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=2
// +kubebuilder:validation:MaxLength=53
// +kubebuilder:validation:Pattern=`^[a-z0-9][a-z0-9-]*[a-z0-9]$`
TenantID string `json:"tenantId"`
// MemoryMB — лимит RAM для ElasticMQ pod в мегабайтах (default: 64).
// ElasticMQ Native (GraalVM) idle потребляет ~30MB.
// +kubebuilder:default=64
// +kubebuilder:validation:Minimum=32
// +kubebuilder:validation:Maximum=1024
MemoryMB int32 `json:"memoryMB,omitempty"`
// StorageMB — размер PVC для H2 persistence в мегабайтах (default: 512).
// H2 хранит очереди и сообщения, переживает рестарт pod.
// +kubebuilder:default=512
// +kubebuilder:validation:Minimum=128
// +kubebuilder:validation:Maximum=10240
StorageMB int32 `json:"storageMB,omitempty"`
// Persistence — сохранять сообщения на диск (H2 через PVC).
// true (default) — сообщения переживают рестарт pod.
// false — только in-memory, при рестарте сообщения теряются.
// +kubebuilder:default=true
Persistence bool `json:"persistence"`
}
// QueueServicePhase — текущая фаза жизненного цикла инстанса.
type QueueServicePhase string
const (
// QueueServicePhasePending — CR создан, начинается подготовка ресурсов
QueueServicePhasePending QueueServicePhase = "Pending"
// QueueServicePhaseProvisioning — ресурсы создаются (PVC, Secret, Deployment, Ingress)
QueueServicePhaseProvisioning QueueServicePhase = "Provisioning"
// QueueServicePhaseReady — ElasticMQ pod запущен, endpoint доступен
QueueServicePhaseReady QueueServicePhase = "Ready"
// QueueServicePhaseFailed — ошибка при создании или падение pod
QueueServicePhaseFailed QueueServicePhase = "Failed"
)
// QueueServiceStatus — наблюдаемое состояние инстанса (заполняет контроллер).
type QueueServiceStatus struct {
// Phase — текущая фаза: Pending, Provisioning, Ready, Failed
Phase QueueServicePhase `json:"phase,omitempty"`
// Endpoint — публичный SQS endpoint тенанта.
// Формат: https://{SQS_EXTERNAL_HOST}/sqs/{tenantId}
// Используется AWS SDK как endpoint_url.
Endpoint string `json:"endpoint,omitempty"`
// SecretName — имя k8s Secret с credentials (accessKey, secretKey).
// Secret находится в namespace sless-fn-{tenantId}.
SecretName string `json:"secretName,omitempty"`
// Message — человекочитаемое сообщение об ошибке или статусе.
Message string `json:"message,omitempty"`
// Conditions — стандартные k8s conditions для интеграции с инструментами.
Conditions []metav1.Condition `json:"conditions,omitempty"`
// ReadyAt — время когда инстанс перешёл в фазу Ready.
ReadyAt *metav1.Time `json:"readyAt,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="TenantID",type=string,JSONPath=`.spec.tenantId`
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Endpoint",type=string,JSONPath=`.status.endpoint`
// +kubebuilder:printcolumn:name="Memory",type=integer,JSONPath=`.spec.memoryMB`
// +kubebuilder:printcolumn:name="Age",type=date,JSONPath=`.metadata.creationTimestamp`
// +kubebuilder:resource:shortName=qs
// QueueService — CRD для managed SQS-совместимого инстанса очередей.
// Оператор создаёт ElasticMQ pod + PVC + Service + Ingress + credentials Secret.
type QueueService struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec QueueServiceSpec `json:"spec,omitempty"`
Status QueueServiceStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// QueueServiceList — список QueueService объектов.
type QueueServiceList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []QueueService `json:"items"`
}
// DeepCopyObject реализует runtime.Object для QueueService.
func (q *QueueService) DeepCopyObject() runtime.Object {
if q == nil {
return nil
}
out := new(QueueService)
q.DeepCopyInto(out)
return out
}
// DeepCopyInto копирует QueueService в out.
func (q *QueueService) DeepCopyInto(out *QueueService) {
*out = *q
out.TypeMeta = q.TypeMeta
q.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = q.Spec
q.Status.DeepCopyInto(&out.Status)
}
// DeepCopyObject реализует runtime.Object для QueueServiceList.
func (q *QueueServiceList) DeepCopyObject() runtime.Object {
if q == nil {
return nil
}
out := new(QueueServiceList)
q.DeepCopyInto(out)
return out
}
// DeepCopyInto копирует QueueServiceList в out.
func (q *QueueServiceList) DeepCopyInto(out *QueueServiceList) {
*out = *q
out.TypeMeta = q.TypeMeta
q.ListMeta.DeepCopyInto(&out.ListMeta)
if q.Items != nil {
in, out := &q.Items, &out.Items
*out = make([]QueueService, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopyInto копирует QueueServiceStatus в out.
func (s *QueueServiceStatus) DeepCopyInto(out *QueueServiceStatus) {
*out = *s
if s.Conditions != nil {
in, out := &s.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in))
copy(*out, *in)
}
if s.ReadyAt != nil {
in, out := &s.ReadyAt, &out.ReadyAt
*out = (*in).DeepCopy()
}
}
func init() {
SchemeBuilder.Register(&QueueService{}, &QueueServiceList{})
}