feat(ha): leader-election via Redis lock + /ready endpoint (active/passive, off by default)
- app/leader: Elector (SETNX lock + PEXPIRE heartbeat via Lua, IsLeader) - app/persistence: TryAcquireLock/RefreshLock/ReleaseLock (no-op when Redis absent) - app/router: /ready -> 200 if ReadyFunc nil/true, else 503 (both planes) - app/cmd: --leader-election / SHARED_SQS_LEADER_ELECTION, --leader-ttl, --leader-interval; ReadyFunc wired only when enabled, lock released on graceful shutdown - default OFF: single-instance behaviour unchanged
This commit is contained in:
@@ -44,6 +44,9 @@ func NewDataPlane(tenantStore *tenant.TenantStore) http.Handler {
|
||||
// /health — публичный, без auth
|
||||
r.HandleFunc("/health", health).Methods("GET")
|
||||
|
||||
// /ready — готовность инстанса (для failover/балансировщика)
|
||||
r.HandleFunc("/ready", readyHandler).Methods("GET")
|
||||
|
||||
// /static — логотип, favicon для публичных страниц
|
||||
r.PathPrefix("/static").Handler(ui.StaticHandler()).Methods("GET")
|
||||
|
||||
@@ -83,6 +86,9 @@ func NewControlPlane(tenantStore *tenant.TenantStore, adminToken string) http.Ha
|
||||
// /health — для проверки control-plane локально
|
||||
r.HandleFunc("/health", health).Methods("GET")
|
||||
|
||||
// /ready — готовность (лидер ли)
|
||||
r.HandleFunc("/ready", readyHandler).Methods("GET")
|
||||
|
||||
// /static — нужен UI-консоли (логотип/favicon)
|
||||
r.PathPrefix("/static").Handler(ui.StaticHandler()).Methods("GET")
|
||||
|
||||
@@ -205,6 +211,22 @@ func health(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, `{"status":"ok","version":%q}`, models.Version)
|
||||
}
|
||||
|
||||
// ReadyFunc — если задана, /ready отдаёт 200 при true и 503 иначе (для failover/LB).
|
||||
// Задаётся в main при включённом leader-election. nil → всегда готовы.
|
||||
var ReadyFunc func() bool
|
||||
|
||||
// readyHandler — готовность инстанса обслуживать трафик (для балансировщика/failover).
|
||||
// Пассивный инстанс отдаёт 503, чтобы LB не направлял на него трафик.
|
||||
func readyHandler(w http.ResponseWriter, req *http.Request) {
|
||||
if ReadyFunc != nil && !ReadyFunc() {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`{"status":"ready"}`))
|
||||
}
|
||||
|
||||
// metricsAuth — оборачивает /metrics в проверку Bearer admin-токена.
|
||||
// В отличие от публичного /health, метрики содержат per-tenant данные
|
||||
// (accessKey, число очередей/сообщений) и не должны быть доступны без auth.
|
||||
|
||||
Reference in New Issue
Block a user