refactor(sqs-operator): переделка через Operator SDK v1.37.0

- operator-sdk init + create api (QueueService kind, group sqs, v1alpha1)
- Стандартная kubebuilder структура: cmd/, internal/controller/, config/
- CRD types с kubebuilder маркерами (validation, defaults, printcolumns)
- Reconciler перенесён из ручного кода в internal/controller/
- controller-gen v0.17.0 (совместимость с Go 1.26.1)
- Автогенерация: deepcopy, CRD YAML, RBAC ClusterRole
- Удалены все ручные файлы (controllers/, main.go, deployments/)
This commit is contained in:
Naeel
2026-04-07 14:25:14 +03:00
parent cbe61d9f62
commit 66dcd99465
53 changed files with 2459 additions and 973 deletions
@@ -0,0 +1,36 @@
/*
Copyright 2026.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package v1alpha1 contains API Schema definitions for the sqs v1alpha1 API group
// +kubebuilder:object:generate=true
// +groupName=sqs.kube5s.ru
package v1alpha1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "sqs.kube5s.ru", Version: "v1alpha1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
+57 -126
View File
@@ -1,107 +1,95 @@
// 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}.
// Изменён: 2026-04-07
// queueservice_types.go — CRD типы для QueueService.
// Описывает Spec (желаемое состояние) и Status (наблюдаемое состояние) ElasticMQ инстанса тенанта.
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 — текущая фаза жизненного цикла инстанса.
// QueueServicePhase — фаза жизненного цикла QueueService инстанса.
type QueueServicePhase string
const (
// QueueServicePhasePending — CR создан, начинается подготовка ресурсов
// QueueServicePhasePending — ресурс создан, ожидает провизионирования.
QueueServicePhasePending QueueServicePhase = "Pending"
// QueueServicePhaseProvisioning — ресурсы создаются (PVC, Secret, Deployment, Ingress)
// QueueServicePhaseProvisioning — k8s ресурсы созданы, ожидаем готовности pod.
QueueServicePhaseProvisioning QueueServicePhase = "Provisioning"
// QueueServicePhaseReady — ElasticMQ pod запущен, endpoint доступен
// QueueServicePhaseReady — ElasticMQ pod запущен и отвечает, endpoint доступен.
QueueServicePhaseReady QueueServicePhase = "Ready"
// QueueServicePhaseFailed — ошибка при создании или падение pod
// QueueServicePhaseFailed — ошибка провизионирования или pod упал.
QueueServicePhaseFailed QueueServicePhase = "Failed"
)
// QueueServiceStatus — наблюдаемое состояние инстанса (заполняет контроллер).
// QueueServiceSpec — желаемое состояние QueueService (параметры тенанта).
type QueueServiceSpec struct {
// TenantID — уникальный идентификатор тенанта.
// Используется в именах ресурсов (sqs-{tenantId}), namespace (sless-fn-{tenantId}),
// и в Ingress path (/sqs/{tenantId}).
// +kubebuilder:validation:Required
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=63
// +kubebuilder:validation:Pattern=`^[a-z0-9]([a-z0-9-]*[a-z0-9])?$`
TenantID string `json:"tenantId"`
// MemoryMB — лимит памяти для ElasticMQ pod в мегабайтах.
// ElasticMQ Native (GraalVM) в idle потребляет ~30MB.
// +kubebuilder:default=64
// +kubebuilder:validation:Minimum=32
// +kubebuilder:validation:Maximum=1024
// +optional
MemoryMB int32 `json:"memoryMB,omitempty"`
// StorageMB — размер PVC для H2 persistence в мегабайтах.
// H2 БД хранит очереди и сообщения между рестартами.
// +kubebuilder:default=512
// +kubebuilder:validation:Minimum=128
// +kubebuilder:validation:Maximum=10240
// +optional
StorageMB int32 `json:"storageMB,omitempty"`
// Persistence — включить H2 persistence для сохранения сообщений при рестарте pod.
// Если false — сообщения in-memory only, теряются при перезапуске.
// +kubebuilder:default=true
// +optional
Persistence bool `json:"persistence,omitempty"`
}
// QueueServiceStatus — наблюдаемое состояние QueueService.
type QueueServiceStatus struct {
// Phase — текущая фаза: Pending, Provisioning, Ready, Failed
// Phase — текущая фаза жизненного цикла.
// +kubebuilder:validation:Enum=Pending;Provisioning;Ready;Failed
// +optional
Phase QueueServicePhase `json:"phase,omitempty"`
// Endpoint — публичный SQS endpoint тенанта.
// Endpoint — публичный URL для SQS API тенанта (заполняется при Ready).
// Формат: https://{SQS_EXTERNAL_HOST}/sqs/{tenantId}
// Используется AWS SDK как endpoint_url.
// +optional
Endpoint string `json:"endpoint,omitempty"`
// SecretName — имя k8s Secret с credentials (accessKey, secretKey).
// Secret находится в namespace sless-fn-{tenantId}.
// SecretName — имя k8s Secret с credentials (accessKey/secretKey).
// +optional
SecretName string `json:"secretName,omitempty"`
// Message — человекочитаемое сообщение об ошибке или статусе.
// Message — описание ошибки или промежуточного статуса.
// +optional
Message string `json:"message,omitempty"`
// Conditions — стандартные k8s conditions для интеграции с инструментами.
Conditions []metav1.Condition `json:"conditions,omitempty"`
// ReadyAt — время когда инстанс перешёл в фазу Ready.
// +optional
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="Tenant",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.
// QueueService — управляет жизненным циклом ElasticMQ инстанса для тенанта.
// Каждый QueueService CR = один ElasticMQ pod + Service + Ingress + PVC + Secret.
type QueueService struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
@@ -112,70 +100,13 @@ type QueueService struct {
// +kubebuilder:object:root=true
// QueueServiceList — список QueueService объектов.
// 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{})
}
@@ -0,0 +1,118 @@
//go:build !ignore_autogenerated
/*
Copyright 2026.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by controller-gen. DO NOT EDIT.
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *QueueService) DeepCopyInto(out *QueueService) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueueService.
func (in *QueueService) DeepCopy() *QueueService {
if in == nil {
return nil
}
out := new(QueueService)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *QueueService) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *QueueServiceList) DeepCopyInto(out *QueueServiceList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]QueueService, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueueServiceList.
func (in *QueueServiceList) DeepCopy() *QueueServiceList {
if in == nil {
return nil
}
out := new(QueueServiceList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *QueueServiceList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *QueueServiceSpec) DeepCopyInto(out *QueueServiceSpec) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueueServiceSpec.
func (in *QueueServiceSpec) DeepCopy() *QueueServiceSpec {
if in == nil {
return nil
}
out := new(QueueServiceSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *QueueServiceStatus) DeepCopyInto(out *QueueServiceStatus) {
*out = *in
if in.ReadyAt != nil {
in, out := &in.ReadyAt, &out.ReadyAt
*out = (*in).DeepCopy()
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new QueueServiceStatus.
func (in *QueueServiceStatus) DeepCopy() *QueueServiceStatus {
if in == nil {
return nil
}
out := new(QueueServiceStatus)
in.DeepCopyInto(out)
return out
}