doc: SQS Operator plan — ElasticMQ per-tenant, 10 этапов
This commit is contained in:
@@ -0,0 +1,426 @@
|
|||||||
|
# SQS Operator — План реализации
|
||||||
|
# Дата: 2026-04-07
|
||||||
|
# Агент: Claude Opus 4.6
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
Managed SQS-совместимый сервис очередей сообщений.
|
||||||
|
Каждый тенант облачного провайдера получает изолированный инстанс (ElasticMQ).
|
||||||
|
Работает через стандартный AWS SDK (Go/Python/Java/JS) — меняется только endpoint.
|
||||||
|
|
||||||
|
## Решения (согласованы с пользователем)
|
||||||
|
- **Модель**: инстанс на тенанта (Вариант A) — изоляция, падение одного не влияет на остальных
|
||||||
|
- **Backend**: ElasticMQ Native (GraalVM) — `softwaremill/elasticmq-native`
|
||||||
|
- **Routing**: path-based — `sqs.kube5s.ru/sqs/{tenant}/...`
|
||||||
|
- **Auth**: Bearer token (существующий у тенанта), абстрагирован для будущей замены на ЛК
|
||||||
|
- **Namespace**: существующий `sless-fn-{tenant}` — ElasticMQ pod рядом с функциями тенанта
|
||||||
|
- **DNS**: `sqs.kube5s.ru` → 185.247.187.147 (создано, резолвится)
|
||||||
|
- **Persistence**: H2 (встроенная в ElasticMQ), через PVC
|
||||||
|
- **Config**: `SQS_EXTERNAL_HOST` в ConfigMap оператора — настраиваемый хост (dev → prod)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Архитектура
|
||||||
|
|
||||||
|
```
|
||||||
|
Terraform "sless_queue_service"
|
||||||
|
→ REST API (sless-operator :9090)
|
||||||
|
→ POST /api/v1/queue-services
|
||||||
|
→ создаёт CRD QueueService в K8s
|
||||||
|
→ QueueServiceReconciler (контроллер в sless-operator)
|
||||||
|
→ создаёт в namespace sless-fn-{tenant}:
|
||||||
|
- ConfigMap (elasticmq.conf)
|
||||||
|
- PVC (persistence H2)
|
||||||
|
- Deployment (ElasticMQ Native pod)
|
||||||
|
- Service (ClusterIP :9324)
|
||||||
|
- Secret (accessKey/secretKey для тенанта)
|
||||||
|
→ Ingress на sqs.kube5s.ru/sqs/{tenant}/ → Service :9324
|
||||||
|
→ Status.Endpoint = https://sqs.kube5s.ru/sqs/{tenant}
|
||||||
|
→ Status.Phase = Ready
|
||||||
|
```
|
||||||
|
|
||||||
|
Клиент использует:
|
||||||
|
```python
|
||||||
|
import boto3
|
||||||
|
sqs = boto3.client(sqs,
|
||||||
|
endpoint_url=https://sqs.kube5s.ru/sqs/my-tenant,
|
||||||
|
aws_access_key_id=xxx,
|
||||||
|
aws_secret_access_key=yyy,
|
||||||
|
region_name=ru-msk-1)
|
||||||
|
queue = sqs.create_queue(QueueName=my-queue)
|
||||||
|
sqs.send_message(QueueUrl=queue[QueueUrl], MessageBody=hello)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Этапы реализации
|
||||||
|
|
||||||
|
### Этап 1: CRD QueueService
|
||||||
|
**Файл**: `api/v1alpha1/queueservice_types.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
// QueueServiceSpec — желаемое состояние инстанса очередей тенанта
|
||||||
|
type QueueServiceSpec struct {
|
||||||
|
// TenantID — уникальный ID тенанта облачного провайдера
|
||||||
|
// +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 — лимит RAM для ElasticMQ (default: 64)
|
||||||
|
// +kubebuilder:default=64
|
||||||
|
// +kubebuilder:validation:Minimum=32
|
||||||
|
// +kubebuilder:validation:Maximum=1024
|
||||||
|
MemoryMB int32 `json:"memoryMB,omitempty"`
|
||||||
|
|
||||||
|
// StorageMB — размер PVC для H2 persistence (default: 512)
|
||||||
|
// +kubebuilder:default=512
|
||||||
|
// +kubebuilder:validation:Minimum=128
|
||||||
|
// +kubebuilder:validation:Maximum=10240
|
||||||
|
StorageMB int32 `json:"storageMB,omitempty"`
|
||||||
|
|
||||||
|
// Persistence — включить сохранение сообщений на диск (default: true)
|
||||||
|
// Если false — только in-memory, сообщения теряются при рестарте
|
||||||
|
// +kubebuilder:default=true
|
||||||
|
Persistence bool `json:"persistence"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueueServicePhase — фаза жизненного цикла инстанса
|
||||||
|
type QueueServicePhase string
|
||||||
|
const (
|
||||||
|
QueueServicePhasePending QueueServicePhase = "Pending"
|
||||||
|
QueueServicePhaseProvisioning QueueServicePhase = "Provisioning"
|
||||||
|
QueueServicePhaseReady QueueServicePhase = "Ready"
|
||||||
|
QueueServicePhaseFailed QueueServicePhase = "Failed"
|
||||||
|
QueueServicePhaseDeleting QueueServicePhase = "Deleting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// QueueServiceStatus — наблюдаемое состояние
|
||||||
|
type QueueServiceStatus struct {
|
||||||
|
Phase QueueServicePhase `json:"phase,omitempty"`
|
||||||
|
Endpoint string `json:"endpoint,omitempty"` // https://sqs.kube5s.ru/sqs/{tenantId}
|
||||||
|
SecretName string `json:"secretName,omitempty"` // имя Secret с credentials
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||||
|
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="Age",type=date,JSONPath=`.metadata.creationTimestamp`
|
||||||
|
type QueueService struct { ... }
|
||||||
|
type QueueServiceList struct { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Действия**:
|
||||||
|
1. Создать файл `api/v1alpha1/queueservice_types.go`
|
||||||
|
2. Добавить `init()` → `SchemeBuilder.Register(&QueueService{}, &QueueServiceList{})`
|
||||||
|
3. Запустить `make manifests` — сгенерирует CRD YAML + deepcopy
|
||||||
|
4. Применить CRD: `kubectl apply -f config/crd/bases/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 2: ElasticMQ Config Generator
|
||||||
|
**Файл**: `internal/sqs/elasticmq_config.go`
|
||||||
|
|
||||||
|
Генерирует HOCON конфиг для ElasticMQ:
|
||||||
|
```go
|
||||||
|
func GenerateElasticMQConfig(tenantID, externalHost string, persistence bool) string
|
||||||
|
```
|
||||||
|
|
||||||
|
Содержимое конфига:
|
||||||
|
```hocon
|
||||||
|
include classpath("application.conf")
|
||||||
|
node-address {
|
||||||
|
protocol = https
|
||||||
|
host = {SQS_EXTERNAL_HOST}
|
||||||
|
port = 443
|
||||||
|
context-path = "/sqs/{tenantID}"
|
||||||
|
}
|
||||||
|
rest-sqs {
|
||||||
|
enabled = true
|
||||||
|
bind-port = 9324
|
||||||
|
bind-hostname = "0.0.0.0"
|
||||||
|
sqs-limits = strict
|
||||||
|
}
|
||||||
|
messages-storage {
|
||||||
|
enabled = {persistence} // true/false
|
||||||
|
uri = "jdbc:h2:/data/elasticmq"
|
||||||
|
}
|
||||||
|
aws {
|
||||||
|
region = ru-msk-1
|
||||||
|
accountId = {tenantID}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 3: Credentials Generator
|
||||||
|
**Файл**: `internal/sqs/credentials.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
// GenerateSQSCredentials — создаёт пару accessKey/secretKey для тенанта.
|
||||||
|
// accessKey: SQSAK{tenantID}_{random8}
|
||||||
|
// secretKey: crypto/rand 32 bytes → base64
|
||||||
|
func GenerateSQSCredentials(tenantID string) (accessKey, secretKey string, err error)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 4: Controller
|
||||||
|
**Файл**: `controllers/queueservice_controller.go`
|
||||||
|
|
||||||
|
Структура:
|
||||||
|
```go
|
||||||
|
type QueueServiceReconciler struct {
|
||||||
|
client.Client
|
||||||
|
Scheme *runtime.Scheme
|
||||||
|
KubeClient kubernetes.Interface
|
||||||
|
SQSExternalHost string // из env SQS_EXTERNAL_HOST
|
||||||
|
Log logr.Logger
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Reconcile loop:
|
||||||
|
```
|
||||||
|
1. GET QueueService CR
|
||||||
|
2. IF deleting:
|
||||||
|
a. Delete Deployment sqs-{tenantId}
|
||||||
|
b. Delete Service sqs-svc-{tenantId}
|
||||||
|
c. Delete ConfigMap sqs-cfg-{tenantId}
|
||||||
|
d. Delete Secret sqs-creds-{tenantId}
|
||||||
|
e. НЕ удалять PVC (данные сохраняются, удаляются вручную)
|
||||||
|
f. Remove finalizer sless.kube5s.ru/sqs-finalizer
|
||||||
|
g. RETURN
|
||||||
|
3. IF no finalizer → add finalizer, set Phase=Pending
|
||||||
|
4. IF Phase=Pending:
|
||||||
|
a. Ensure namespace sless-fn-{tenantId} exists
|
||||||
|
b. Generate credentials → create Secret sqs-creds-{tenantId}
|
||||||
|
c. Generate elasticmq.conf → create ConfigMap sqs-cfg-{tenantId}
|
||||||
|
d. Create PVC sqs-data-{tenantId} (StorageMB)
|
||||||
|
e. Set Phase=Provisioning, requeue
|
||||||
|
5. IF Phase=Provisioning:
|
||||||
|
a. Create/Update Deployment sqs-{tenantId}:
|
||||||
|
- image: softwaremill/elasticmq-native:1.7.1
|
||||||
|
- container port: 9324
|
||||||
|
- volumeMounts:
|
||||||
|
- sqs-cfg-{tenantId} → /opt/elasticmq/custom.conf (subPath)
|
||||||
|
- sqs-data-{tenantId} → /data
|
||||||
|
- env: JAVA_TOOL_OPTIONS=-Dconfig.file=/opt/elasticmq/custom.conf
|
||||||
|
- resources: requests 10m/32Mi, limits 500m/{MemoryMB}Mi
|
||||||
|
- readinessProbe: httpGet /health :9324 (period: 5s)
|
||||||
|
- livenessProbe: httpGet /health :9324 (period: 10s)
|
||||||
|
b. Create Service sqs-svc-{tenantId} → port 9324
|
||||||
|
c. Check: is Deployment Ready? (availableReplicas >= 1)
|
||||||
|
- No → requeue after 3s
|
||||||
|
- Yes → set Phase=Ready, Endpoint, ReadyAt
|
||||||
|
6. IF Phase=Ready:
|
||||||
|
a. Check Deployment health (availableReplicas)
|
||||||
|
b. If unhealthy → Phase=Failed + Message
|
||||||
|
7. IF Phase=Failed:
|
||||||
|
a. Check if Deployment recovered → Phase=Ready
|
||||||
|
b. Else requeue after 30s
|
||||||
|
```
|
||||||
|
|
||||||
|
RBAC markers:
|
||||||
|
```go
|
||||||
|
//+kubebuilder:rbac:groups=sless.kube5s.ru,resources=queueservices,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
//+kubebuilder:rbac:groups=sless.kube5s.ru,resources=queueservices/status,verbs=get;update;patch
|
||||||
|
//+kubebuilder:rbac:groups=sless.kube5s.ru,resources=queueservices/finalizers,verbs=update
|
||||||
|
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
//+kubebuilder:rbac:groups="",resources=services,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
//+kubebuilder:rbac:groups="",resources=persistentvolumeclaims,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
//+kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create
|
||||||
|
```
|
||||||
|
|
||||||
|
SetupWithManager — watch QueueService, own Deployment/Service/ConfigMap/Secret/PVC.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 5: Ingress
|
||||||
|
**Подход**: один Ingress на `sqs.kube5s.ru` с path-based routing.
|
||||||
|
|
||||||
|
Варианты:
|
||||||
|
A) Контроллер создаёт отдельный Ingress на каждого тенанта:
|
||||||
|
```yaml
|
||||||
|
# Ingress sqs-ing-{tenantId} в ns sless-fn-{tenantId}
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: sqs.kube5s.ru
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /sqs/{tenantId}
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: sqs-svc-{tenantId}
|
||||||
|
port: 9324
|
||||||
|
tls:
|
||||||
|
- hosts: [sqs.kube5s.ru]
|
||||||
|
secretName: sqs-kube5s-ru-tls
|
||||||
|
```
|
||||||
|
|
||||||
|
B) Один Ingress + nginx rewrite в оператор, оператор проксирует.
|
||||||
|
|
||||||
|
**Рекомендация**: вариант A — по Ingress на тенанта. Nginx Ingress Controller мержит
|
||||||
|
правила автоматически.
|
||||||
|
|
||||||
|
RBAC добавить: `networking.k8s.io/ingresses`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 6: Регистрация в main.go
|
||||||
|
|
||||||
|
1. Добавить в `internal/config/config.go`:
|
||||||
|
```go
|
||||||
|
SQSExternalHost string // env SQS_EXTERNAL_HOST, default: "sqs.kube5s.ru"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. В `main.go` — зарегистрировать контроллер:
|
||||||
|
```go
|
||||||
|
if err = (&controllers.QueueServiceReconciler{
|
||||||
|
Client: mgr.GetClient(),
|
||||||
|
Scheme: mgr.GetScheme(),
|
||||||
|
KubeClient: kubernetes.NewForConfigOrDie(mgr.GetConfig()),
|
||||||
|
SQSExternalHost: cfg.SQSExternalHost,
|
||||||
|
}).SetupWithManager(mgr); err != nil {
|
||||||
|
log.Error("unable to create controller", "controller", "QueueService", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 7: REST API Handlers
|
||||||
|
**Файл**: `internal/api/handler/queueservice_handler.go`
|
||||||
|
|
||||||
|
Эндпоинты:
|
||||||
|
```
|
||||||
|
POST /api/v1/queue-services — создать QueueService CR
|
||||||
|
GET /api/v1/queue-services — список QueueService для тенанта (по namespace)
|
||||||
|
GET /api/v1/queue-services/{name} — статус конкретного инстанса
|
||||||
|
DELETE /api/v1/queue-services/{name} — удалить QueueService CR
|
||||||
|
```
|
||||||
|
|
||||||
|
POST body:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-queues",
|
||||||
|
"memoryMB": 64,
|
||||||
|
"storageMB": 512,
|
||||||
|
"persistence": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
GET response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-queues",
|
||||||
|
"phase": "Ready",
|
||||||
|
"endpoint": "https://sqs.kube5s.ru/sqs/my-tenant",
|
||||||
|
"accessKey": "SQSAKmy-tenant_a1b2c3d4",
|
||||||
|
"secretKey": "...",
|
||||||
|
"createdAt": "2026-04-07T13:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Auth**: существующий middleware (Bearer token → namespace mapping).
|
||||||
|
|
||||||
|
Добавить routes в `internal/api/router.go`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 8: Deployment
|
||||||
|
**Файл**: `deployments/k8s/operator.yaml`
|
||||||
|
|
||||||
|
Добавить в ConfigMap:
|
||||||
|
```yaml
|
||||||
|
SQS_EXTERNAL_HOST: sqs.kube5s.ru
|
||||||
|
SQS_ELASTICMQ_IMAGE: softwaremill/elasticmq-native:1.7.1
|
||||||
|
```
|
||||||
|
|
||||||
|
RBAC: обновить ClusterRole (или использовать `make manifests` → `config/rbac/role.yaml`).
|
||||||
|
|
||||||
|
Применить новый CRD:
|
||||||
|
```bash
|
||||||
|
kubectl apply -f config/crd/bases/sless.kube5s.ru_queueservices.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 9: Сборка + Деплой + Тест
|
||||||
|
1. `make manifests` — генерация CRD + RBAC
|
||||||
|
2. `go build -o bin/sless-operator .` → Docker build → push
|
||||||
|
3. `kubectl apply -f deployments/k8s/operator.yaml`
|
||||||
|
4. Тест:
|
||||||
|
```bash
|
||||||
|
# Создать инстанс через API
|
||||||
|
curl -X POST https://sless.kube5s.ru/api/v1/queue-services \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d memoryMB:64
|
||||||
|
|
||||||
|
# Дождаться Ready
|
||||||
|
curl https://sless.kube5s.ru/api/v1/queue-services/test-qs \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
|
||||||
|
# Проверить SQS API через AWS CLI
|
||||||
|
aws sqs create-queue \
|
||||||
|
--queue-name test-queue \
|
||||||
|
--endpoint-url https://sqs.kube5s.ru/sqs/test-tenant \
|
||||||
|
--region ru-msk-1
|
||||||
|
|
||||||
|
aws sqs send-message \
|
||||||
|
--queue-url https://sqs.kube5s.ru/sqs/test-tenant/queue/test-queue \
|
||||||
|
--message-body "hello from managed SQS" \
|
||||||
|
--endpoint-url https://sqs.kube5s.ru/sqs/test-tenant \
|
||||||
|
--region ru-msk-1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Этап 10: Terraform Resource (отдельная репа)
|
||||||
|
```hcl
|
||||||
|
resource "sless_queue_service" "main" {
|
||||||
|
name = "production-queues"
|
||||||
|
memory_mb = 128
|
||||||
|
storage_mb = 1024
|
||||||
|
}
|
||||||
|
|
||||||
|
output "sqs_endpoint" {
|
||||||
|
value = sless_queue_service.main.endpoint
|
||||||
|
}
|
||||||
|
output "sqs_access_key" {
|
||||||
|
value = sless_queue_service.main.access_key
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Файловая карта (новые файлы)
|
||||||
|
|
||||||
|
| # | Файл | Назначение |
|
||||||
|
|---|------|-----------|
|
||||||
|
| 1 | `api/v1alpha1/queueservice_types.go` | CRD types |
|
||||||
|
| 2 | `internal/sqs/elasticmq_config.go` | Генератор HOCON конфига |
|
||||||
|
| 3 | `internal/sqs/credentials.go` | Генератор accessKey/secretKey |
|
||||||
|
| 4 | `controllers/queueservice_controller.go` | Reconciler |
|
||||||
|
| 5 | `internal/api/handler/queueservice_handler.go` | REST API handlers |
|
||||||
|
| 6 | `internal/api/router.go` | Добавить routes (модификация) |
|
||||||
|
| 7 | `internal/config/config.go` | Добавить SQSExternalHost (модификация) |
|
||||||
|
| 8 | `main.go` | Регистрация контроллера (модификация) |
|
||||||
|
| 9 | `deployments/k8s/operator.yaml` | ConfigMap + RBAC (модификация) |
|
||||||
|
|
||||||
|
## Зависимости (go.mod)
|
||||||
|
- Новых зависимостей НЕТ. Всё уже есть: controller-runtime, client-go, kubernetes.
|
||||||
|
|
||||||
|
## Открытые вопросы (для будущего)
|
||||||
|
- Auth sidecar (AWS Signature V4) — пока Bearer token, потом если надо
|
||||||
|
- Мониторинг (Prometheus metrics per tenant) — после MVP
|
||||||
|
- Autoscaling (вертикальный — увеличить memory по нагрузке) — после MVP
|
||||||
|
- Backup/restore PVC — после MVP
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
---
|
||||||
|
# Агент: Claude Opus 4.6 | SQS Operator Planning
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
Пользователь попросил создать managed SQS-совместимый сервис очередей для облачного провайдера.
|
||||||
|
|
||||||
|
## Анализ вариантов backend
|
||||||
|
1. **ElasticMQ** — Scala, 2.8k stars, persistence (H2), SQS API, native image (GraalVM), Apache-2.0, активная разработка (v1.7.1 — 2 дня назад). ПОБЕДИТЕЛЬ.
|
||||||
|
2. **GoAWS** — Go, 834 stars, НЕТ persistence, только in-memory. Не production.
|
||||||
|
3. **LocalStack** — Python, 56k stars, эмуляция всего AWS. Избыточно.
|
||||||
|
|
||||||
|
## Анализ архитектуры
|
||||||
|
- **Вариант A (инстанс на тенанта)** — выбран. Изоляция, отказоустойчивость, ~30MB RAM idle.
|
||||||
|
- **Вариант B (shared)** — отклонён: "если наебнётся — то у всех".
|
||||||
|
|
||||||
|
## Ключевые решения
|
||||||
|
- Routing: path-based `sqs.kube5s.ru/sqs/{tenant}/` (не subdomain — не нужен wildcard DNS)
|
||||||
|
- Auth: Bearer token (существующий), абстрагирован для замены на ЛК
|
||||||
|
- Namespace: `sless-fn-{tenant}` (уже создаётся для serverless функций)
|
||||||
|
- Config: `SQS_EXTERNAL_HOST` — настраиваемый, для смены при деплое в облако
|
||||||
|
- DNS: создана A-запись `sqs.kube5s.ru` → 185.247.187.147
|
||||||
|
|
||||||
|
## Результат
|
||||||
|
Создан подробный план `doc/sqs-operator-plan.md` — 10 этапов, 9 файлов, готов для реализации Sonnet.
|
||||||
Reference in New Issue
Block a user