feat(shared-sqs): Redis write-through persistence, RollingUpdate deploy (v0.1.11)
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
// app/cmd/goaws.go
|
||||
// Entry point — shared-sqs server
|
||||
// Updated: 2026-04-09 — добавлены TenantStore, admin token, graceful shutdown (Trap #13)
|
||||
// Updated: 2026-04-10 — добавлена Redis persistence (write-through cache)
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -15,6 +16,7 @@ import (
|
||||
"shared-sqs/app/conf"
|
||||
"shared-sqs/app/gosqs"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/router"
|
||||
"shared-sqs/app/tenant"
|
||||
|
||||
@@ -77,6 +79,47 @@ func main() {
|
||||
|
||||
// Инициализация in-memory TenantStore
|
||||
tenantStore := tenant.NewTenantStore()
|
||||
|
||||
// Подключение к Redis (если задан REDIS_ADDR)
|
||||
// При ошибке — предупреждение, но продолжаем в memory-only режиме
|
||||
redisAddr := os.Getenv("REDIS_ADDR")
|
||||
redisUser := os.Getenv("REDIS_USER")
|
||||
redisPass := os.Getenv("REDIS_PASSWORD")
|
||||
if redisAddr != "" {
|
||||
if err := persistence.Connect(redisAddr, redisUser, redisPass); err != nil {
|
||||
log.Warnf("Не удалось подключиться к Redis: %v — работаем в memory-only режиме", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Восстановление состояния из Redis (тенанты + очереди)
|
||||
if persistence.Client != nil {
|
||||
// Загружаем тенантов
|
||||
tenantsRaw, err := persistence.LoadAllTenantsRaw()
|
||||
if err != nil {
|
||||
log.Warnf("Ошибка загрузки тенантов из Redis: %v", err)
|
||||
} else {
|
||||
for _, jsonBytes := range tenantsRaw {
|
||||
var t tenant.Tenant
|
||||
if err := json.Unmarshal(jsonBytes, &t); err != nil {
|
||||
log.Errorf("Ошибка десериализации тенанта: %v", err)
|
||||
continue
|
||||
}
|
||||
tenantStore.LoadTenant(&t)
|
||||
}
|
||||
}
|
||||
// Загружаем очереди
|
||||
queues, err := persistence.LoadAllQueues()
|
||||
if err != nil {
|
||||
log.Warnf("Ошибка загрузки очередей из Redis: %v", err)
|
||||
} else {
|
||||
models.SyncQueues.Lock()
|
||||
for k, q := range queues {
|
||||
models.SyncQueues.Queues[k] = q
|
||||
}
|
||||
models.SyncQueues.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Автосид демо-данных при SHARED_SQS_SEED_DEMO=true
|
||||
if os.Getenv("SHARED_SQS_SEED_DEMO") == "true" {
|
||||
seedDemoData(tenantStore)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-04-09
|
||||
// Изменено: 2026-04-10 — добавлена Redis persistence
|
||||
// CreateQueueV1 — создаёт очередь для тенанта из request context.
|
||||
// Ключ в SyncQueues: "{tenantAccessKey}:{queueName}" для изоляции между тенантами.
|
||||
package gosqs
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -54,8 +55,8 @@ return utils.CreateErrorResponseV1(err.Error(), true)
|
||||
}
|
||||
models.SyncQueues.Queues[key] = queue
|
||||
}
|
||||
models.SyncQueues.Unlock()
|
||||
|
||||
// Сохраняем очередь в Redis пока держим Lock — консистентный снапшот
|
||||
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
|
||||
respStruct := models.CreateQueueResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.CreateQueueResult{QueueUrl: queueUrl},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-04-09
|
||||
// Изменено: 2026-04-10 — добавлена Redis persistence
|
||||
// DeleteMessageV1 — удаляет сообщение из очереди тенанта по ReceiptHandle.
|
||||
package gosqs
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
@@ -49,6 +50,8 @@ func DeleteMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
||||
models.SyncQueues.Queues[key].UnlockGroup(msg.GroupID)
|
||||
models.SyncQueues.Queues[key].Messages = append(models.SyncQueues.Queues[key].Messages[:i], models.SyncQueues.Queues[key].Messages[i+1:]...)
|
||||
delete(models.SyncQueues.Queues[key].Duplicates, msg.DeduplicationID)
|
||||
// Сохраняем очередь в Redis пока держим Lock
|
||||
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
|
||||
respStruct := models.DeleteMessageResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-04-09
|
||||
// Изменено: 2026-04-10 — добавлена Redis persistence
|
||||
// DeleteQueueV1 — удаляет очередь тенанта по tenant-scoped ключу.
|
||||
package gosqs
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -35,6 +36,9 @@ models.SyncQueues.Lock()
|
||||
delete(models.SyncQueues.Queues, key)
|
||||
models.SyncQueues.Unlock()
|
||||
|
||||
// Удаляем из Redis асинхронно
|
||||
persistence.DeleteQueue(key)
|
||||
|
||||
respStruct := models.DeleteQueueResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-04-09
|
||||
// Изменено: 2026-04-10 — добавлена Redis persistence
|
||||
// PurgeQueueV1 — очищает все сообщения в очереди тенанта.
|
||||
package gosqs
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -40,6 +41,8 @@ return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
log.Infof("Purging Queue: %s (tenant: %s)", queueName, t.ID)
|
||||
models.SyncQueues.Queues[key].Messages = nil
|
||||
models.SyncQueues.Queues[key].Duplicates = make(map[string]time.Time)
|
||||
// Сохраняем пустую очередь в Redis пока держим Lock
|
||||
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
|
||||
|
||||
respStruct := models.PurgeQueueResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-04-09
|
||||
// Изменено: 2026-04-10 — добавлена Redis persistence
|
||||
// SendMessageV1 — добавляет сообщение в очередь тенанта.
|
||||
// Ловушка #6: queueName извлекается как ПОСЛЕДНИЙ сегмент URL — при URL вида
|
||||
// http://host/tenantID/queueName последний сегмент = queueName (правильно).
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -90,6 +91,8 @@ log.Debugf("Duplicate message deduplicationId [%s] in queue [%s]", messageDedupl
|
||||
}
|
||||
|
||||
models.SyncQueues.Queues[key].InitDuplicatation(messageDeduplicationID)
|
||||
// Сохраняем очередь в Redis пока держим Lock
|
||||
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
|
||||
models.SyncQueues.Unlock()
|
||||
log.Infof("%s: Queue: %s, Message: %s\n", time.Now().Format("2006-01-02 15:04:05"), queueName, msg.MessageBody)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Изменено: 2026-04-09
|
||||
// Изменено: 2026-04-10 — добавлена Redis persistence
|
||||
// SetQueueAttributesV1 — устанавливает атрибуты очереди тенанта.
|
||||
// Ловушка #9: при RedrivePolicy парсим ARN DLQ и DLQ тоже должна принадлежать тому же тенанту.
|
||||
package gosqs
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -45,6 +46,8 @@ return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
if err := setQueueAttributesV1(queue, requestBody.Attributes); err != nil {
|
||||
return utils.CreateErrorResponseV1(err.Error(), true)
|
||||
}
|
||||
// Сохраняем атрибуты в Redis пока держим Lock (через defer)
|
||||
persistence.SaveQueue(key, queue)
|
||||
|
||||
respStruct := models.SetQueueAttributesResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// app/persistence/redis.go
|
||||
// Redis persistence layer для shared-sqs — write-through cache.
|
||||
// Стратегия: память — источник правды для чтения (быстро),
|
||||
// Redis — источник правды для восстановления после рестарта.
|
||||
// Все записи в Redis асинхронны (горутина) — не блокируют SQS-операции.
|
||||
// Сериализация (json.Marshal) происходит синхронно пока вызывающий держит мьютекс — консистентный снапшот.
|
||||
// Created: 2026-04-10
|
||||
|
||||
package persistence
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"shared-sqs/app/models"
|
||||
)
|
||||
|
||||
// Client — глобальный Redis клиент.
|
||||
// nil означает режим "только память" — все функции тихо no-op.
|
||||
var Client *redis.Client
|
||||
|
||||
const (
|
||||
// redisHashTenants — HASH: tenantID → JSON тенанта
|
||||
redisHashTenants = "ssq:tenants"
|
||||
// redisHashQueues — HASH: queueKey → JSON очереди (включая сообщения)
|
||||
redisHashQueues = "ssq:queues"
|
||||
)
|
||||
|
||||
// Connect — подключается к Redis и проверяет ping.
|
||||
// Если addr пустой — не подключается, остаёмся в memory-only режиме.
|
||||
func Connect(addr, username, password string) error {
|
||||
if addr == "" {
|
||||
log.Info("persistence: REDIS_ADDR не задан, работаем в memory-only режиме")
|
||||
return nil
|
||||
}
|
||||
rdb := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Username: username,
|
||||
Password: password,
|
||||
DB: 0,
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := rdb.Ping(ctx).Err(); err != nil {
|
||||
return fmt.Errorf("redis ping %s: %w", addr, err)
|
||||
}
|
||||
Client = rdb
|
||||
log.Infof("persistence: подключились к Redis %s", addr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// asyncWrite — запускает fn в горутине, перехватывает panic и логирует.
|
||||
// Используется для записей в Redis чтобы не задерживать SQS-ответы.
|
||||
func asyncWrite(fn func()) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorf("persistence: panic в asyncWrite: %v", r)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
|
||||
// SaveQueue — сохраняет очередь (с сообщениями) в Redis асинхронно.
|
||||
// ВАЖНО: вызывать пока вызывающий держит SyncQueues.Lock() — тогда json.Marshal
|
||||
// создаёт консистентный снапшот. Горутина только делает сетевой вызов.
|
||||
func SaveQueue(key string, queue *models.Queue) {
|
||||
if Client == nil {
|
||||
return
|
||||
}
|
||||
// Сериализуем синхронно под мьютексом вызывающего → консистентный снапшот
|
||||
data, err := json.Marshal(queue)
|
||||
if err != nil {
|
||||
log.Errorf("persistence: marshal queue %q: %v", key, err)
|
||||
return
|
||||
}
|
||||
asyncWrite(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := Client.HSet(ctx, redisHashQueues, key, string(data)).Err(); err != nil {
|
||||
log.Errorf("persistence: HSet queue %q: %v", key, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteQueue — удаляет очередь из Redis асинхронно.
|
||||
func DeleteQueue(key string) {
|
||||
if Client == nil {
|
||||
return
|
||||
}
|
||||
asyncWrite(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := Client.HDel(ctx, redisHashQueues, key).Err(); err != nil {
|
||||
log.Errorf("persistence: HDel queue %q: %v", key, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// LoadAllQueues — загружает все очереди из Redis в память при старте сервиса.
|
||||
// Инициализирует nil-maps чтобы избежать panic при deduplication/FIFO операциях.
|
||||
func LoadAllQueues() (map[string]*models.Queue, error) {
|
||||
if Client == nil {
|
||||
return nil, nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
raw, err := Client.HGetAll(ctx, redisHashQueues).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("redis HGetAll queues: %w", err)
|
||||
}
|
||||
queues := make(map[string]*models.Queue, len(raw))
|
||||
for k, v := range raw {
|
||||
var q models.Queue
|
||||
if err := json.Unmarshal([]byte(v), &q); err != nil {
|
||||
log.Errorf("persistence: unmarshal queue %q: %v", k, err)
|
||||
continue
|
||||
}
|
||||
// Инициализируем nil-maps — их json.Unmarshal не создаёт если поле было nil
|
||||
if q.Duplicates == nil {
|
||||
q.Duplicates = make(map[string]time.Time)
|
||||
}
|
||||
if q.FIFOMessages == nil {
|
||||
q.FIFOMessages = make(map[string]int)
|
||||
}
|
||||
if q.FIFOSequenceNumbers == nil {
|
||||
q.FIFOSequenceNumbers = make(map[string]int)
|
||||
}
|
||||
queues[k] = &q
|
||||
}
|
||||
log.Infof("persistence: загружено %d очередей из Redis", len(queues))
|
||||
return queues, nil
|
||||
}
|
||||
|
||||
// SaveTenantRaw — сохраняет тенанта (сырой JSON) в Redis асинхронно.
|
||||
// Принимает []byte чтобы избежать циклического импорта с пакетом tenant.
|
||||
// Сериализацию делает вызывающий (tenant_store.go).
|
||||
func SaveTenantRaw(id string, jsonData []byte) {
|
||||
if Client == nil {
|
||||
return
|
||||
}
|
||||
// Копируем bytes — вызывающий может переиспользовать буфер
|
||||
dataCopy := make([]byte, len(jsonData))
|
||||
copy(dataCopy, jsonData)
|
||||
asyncWrite(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := Client.HSet(ctx, redisHashTenants, id, string(dataCopy)).Err(); err != nil {
|
||||
log.Errorf("persistence: HSet tenant %q: %v", id, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteTenant — удаляет тенанта из Redis асинхронно.
|
||||
func DeleteTenant(id string) {
|
||||
if Client == nil {
|
||||
return
|
||||
}
|
||||
asyncWrite(func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
if err := Client.HDel(ctx, redisHashTenants, id).Err(); err != nil {
|
||||
log.Errorf("persistence: HDel tenant %q: %v", id, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// LoadAllTenantsRaw — загружает всех тенантов из Redis при старте.
|
||||
// Возвращает map[tenantID]rawJSON — десериализацию делает tenant_store.go.
|
||||
func LoadAllTenantsRaw() (map[string][]byte, error) {
|
||||
if Client == nil {
|
||||
return nil, nil
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
raw, err := Client.HGetAll(ctx, redisHashTenants).Result()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("redis HGetAll tenants: %w", err)
|
||||
}
|
||||
result := make(map[string][]byte, len(raw))
|
||||
for id, v := range raw {
|
||||
result[id] = []byte(v)
|
||||
}
|
||||
log.Infof("persistence: загружено %d тенантов из Redis", len(result))
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
// Изменено: 2026-04-09
|
||||
// Изменено: 2026-04-10 — добавлена Redis-персистентность через пакет persistence
|
||||
// Tenant model и in-memory хранилище тенантов для shared-sqs.
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"shared-sqs/app/persistence"
|
||||
)
|
||||
|
||||
// Tenant — модель тенанта shared-sqs.
|
||||
@@ -68,6 +71,11 @@ func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) {
|
||||
s.byAccessKey[t.AccessKey] = t
|
||||
s.mu.Unlock()
|
||||
|
||||
// Сохраняем в Redis асинхронно — сериализуем здесь, вне мьютекса
|
||||
if data, err := json.Marshal(t); err == nil {
|
||||
persistence.SaveTenantRaw(t.ID, data)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
@@ -75,11 +83,12 @@ func (s *TenantStore) Create(name string, maxQueues int) (*Tenant, error) {
|
||||
// Используется только при инициализации демо-данных; не вызывается из user-facing API.
|
||||
func (s *TenantStore) CreateFixed(name string, maxQueues int, tenantID, accessKey, secretKey string) (*Tenant, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if _, exists := s.byID[tenantID]; exists {
|
||||
s.mu.Unlock()
|
||||
return nil, fmt.Errorf("tenant with id %s already exists", tenantID)
|
||||
}
|
||||
if _, exists := s.byAccessKey[accessKey]; exists {
|
||||
s.mu.Unlock()
|
||||
return nil, fmt.Errorf("tenant with access key %s already exists", accessKey)
|
||||
}
|
||||
t := &Tenant{
|
||||
@@ -93,6 +102,13 @@ func (s *TenantStore) CreateFixed(name string, maxQueues int, tenantID, accessKe
|
||||
}
|
||||
s.byID[t.ID] = t
|
||||
s.byAccessKey[t.AccessKey] = t
|
||||
s.mu.Unlock()
|
||||
|
||||
// Сохраняем в Redis асинхронно — seed-данные тоже персистируем
|
||||
if data, err := json.Marshal(t); err == nil {
|
||||
persistence.SaveTenantRaw(t.ID, data)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
@@ -116,16 +132,30 @@ func (s *TenantStore) GetByID(id string) (*Tenant, bool) {
|
||||
// Ловушка #2: если удалить только из одного индекса — orphaned данные и memory leak.
|
||||
func (s *TenantStore) Delete(id string) bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
t, ok := s.byID[id]
|
||||
if !ok {
|
||||
s.mu.Unlock()
|
||||
return false
|
||||
}
|
||||
delete(s.byID, t.ID)
|
||||
delete(s.byAccessKey, t.AccessKey)
|
||||
s.mu.Unlock()
|
||||
|
||||
// Удаляем из Redis асинхронно
|
||||
persistence.DeleteTenant(id)
|
||||
return true
|
||||
}
|
||||
|
||||
// LoadTenant — добавляет тенанта в хранилище без сохранения в Redis.
|
||||
// Используется ТОЛЬКО при старте сервиса для восстановления состояния из Redis.
|
||||
// Не вызывать из user-facing кода — нет дедупликации ключей.
|
||||
func (s *TenantStore) LoadTenant(t *Tenant) {
|
||||
s.mu.Lock()
|
||||
s.byID[t.ID] = t
|
||||
s.byAccessKey[t.AccessKey] = t
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// List — список всех тенантов (для admin GET /tenants).
|
||||
func (s *TenantStore) List() []*Tenant {
|
||||
s.mu.RLock()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# deployments/k8s/deployment.yaml
|
||||
# Deployment shared-sqs — strategy Recreate (in-memory state, Trap #14)
|
||||
# Updated: 2026-04-09
|
||||
# Deployment shared-sqs — strategy RollingUpdate (теперь возможен т.к. Redis хранит состояние)
|
||||
# Updated: 2026-04-10 — добавлена Redis persistence, Recreate → RollingUpdate
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
@@ -11,7 +11,10 @@ metadata:
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate
|
||||
type: RollingUpdate
|
||||
rollingUpdate:
|
||||
maxSurge: 1
|
||||
maxUnavailable: 0
|
||||
selector:
|
||||
matchLabels:
|
||||
app: shared-sqs
|
||||
@@ -22,7 +25,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: shared-sqs
|
||||
image: naeel/shared-sqs:v0.1.8
|
||||
image: naeel/shared-sqs:v0.1.11
|
||||
ports:
|
||||
- containerPort: 4100
|
||||
name: http
|
||||
@@ -34,6 +37,21 @@ spec:
|
||||
key: token
|
||||
- name: SHARED_SQS_SEED_DEMO
|
||||
value: "true"
|
||||
- name: REDIS_ADDR
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shared-sqs-redis
|
||||
key: addr
|
||||
- name: REDIS_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shared-sqs-redis
|
||||
key: user
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shared-sqs-redis
|
||||
key: password
|
||||
resources:
|
||||
requests:
|
||||
memory: "64Mi"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# deployments/k8s/secret.yaml
|
||||
# Секрет для admin token shared-sqs
|
||||
# Секреты для shared-sqs: admin token + Redis credentials
|
||||
# Created: 2026-04-09
|
||||
# ВНИМАНИЕ: заполнить реальным токеном перед деплоем
|
||||
# Updated: 2026-04-10 — добавлен shared-sqs-redis secret
|
||||
# ВНИМАНИЕ: заполнить реальными значениями перед деплоем
|
||||
# kubectl create secret generic shared-sqs-admin --from-literal=token=YOUR_TOKEN -n shared-sqs
|
||||
# kubectl create secret generic shared-sqs-redis --from-literal=addr=HOST:6379 --from-literal=user=default --from-literal=password=PASS -n shared-sqs
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
@@ -11,3 +13,14 @@ metadata:
|
||||
type: Opaque
|
||||
stringData:
|
||||
token: "REPLACE_WITH_REAL_TOKEN"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: shared-sqs-redis
|
||||
namespace: shared-sqs
|
||||
type: Opaque
|
||||
stringData:
|
||||
addr: "REPLACE_WITH_REDIS_ADDR"
|
||||
user: "default"
|
||||
password: "REPLACE_WITH_REDIS_PASSWORD"
|
||||
|
||||
@@ -13,10 +13,14 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/redis/go-redis/v9 v9.18.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
@@ -20,11 +24,15 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
||||
Reference in New Issue
Block a user