feat: sqs-operator — CRD QueueService, контроллер, ElasticMQ per-tenant
This commit is contained in:
@@ -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{})
|
||||
}
|
||||
Reference in New Issue
Block a user