forked from baron/baron-sso
oathkeeper 동작 확인
This commit is contained in:
132
backend/cmd/server/health_monitor.go
Normal file
132
backend/cmd/server/health_monitor.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HTTPProbe struct {
|
||||
name string
|
||||
url string
|
||||
interval time.Duration
|
||||
timeout time.Duration
|
||||
client *http.Client
|
||||
mu sync.RWMutex
|
||||
status string
|
||||
lastError string
|
||||
lastChecked time.Time
|
||||
lastSuccess time.Time
|
||||
}
|
||||
|
||||
type ProbeSnapshot struct {
|
||||
Status string
|
||||
Error string
|
||||
LastChecked time.Time
|
||||
LastSuccess time.Time
|
||||
}
|
||||
|
||||
func NewHTTPProbe(name, url string, interval, timeout time.Duration) *HTTPProbe {
|
||||
if interval <= 0 {
|
||||
interval = 10 * time.Second
|
||||
}
|
||||
if timeout <= 0 {
|
||||
timeout = 2 * time.Second
|
||||
}
|
||||
|
||||
return &HTTPProbe{
|
||||
name: name,
|
||||
url: url,
|
||||
interval: interval,
|
||||
timeout: timeout,
|
||||
client: &http.Client{
|
||||
Timeout: timeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Start는 프로브를 백그라운드에서 주기적으로 실행합니다.
|
||||
func (p *HTTPProbe) Start() {
|
||||
go func() {
|
||||
p.checkOnce()
|
||||
ticker := time.NewTicker(p.interval)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
p.checkOnce()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *HTTPProbe) Snapshot() ProbeSnapshot {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
return ProbeSnapshot{
|
||||
Status: p.status,
|
||||
Error: p.lastError,
|
||||
LastChecked: p.lastChecked,
|
||||
LastSuccess: p.lastSuccess,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *HTTPProbe) StatusText() string {
|
||||
s := p.Snapshot()
|
||||
if s.Status == "ok" {
|
||||
return "ok"
|
||||
}
|
||||
if s.Status == "" {
|
||||
return "unknown"
|
||||
}
|
||||
if s.Error == "" {
|
||||
return "error"
|
||||
}
|
||||
return "error: " + s.Error
|
||||
}
|
||||
|
||||
func (p *HTTPProbe) checkOnce() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), p.timeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, p.url, nil)
|
||||
if err != nil {
|
||||
p.update("error", fmt.Sprintf("request build failed: %v", err), false)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
p.update("error", err.Error(), false)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
p.update("error", fmt.Sprintf("status=%d", resp.StatusCode), false)
|
||||
return
|
||||
}
|
||||
|
||||
p.update("ok", "", true)
|
||||
}
|
||||
|
||||
func (p *HTTPProbe) update(status, errMsg string, success bool) {
|
||||
p.mu.Lock()
|
||||
prevStatus := p.status
|
||||
p.status = status
|
||||
p.lastError = errMsg
|
||||
p.lastChecked = time.Now()
|
||||
if success {
|
||||
p.lastSuccess = p.lastChecked
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
if prevStatus == status {
|
||||
return
|
||||
}
|
||||
if status == "ok" {
|
||||
slog.Info("Service probe recovered", "name", p.name, "url", p.url)
|
||||
return
|
||||
}
|
||||
slog.Error("Service probe failed", "name", p.name, "url", p.url, "error", errMsg)
|
||||
}
|
||||
@@ -158,6 +158,28 @@ func main() {
|
||||
slog.Warn("Failed to connect to Redis. Auth features may fail.", "error", err)
|
||||
}
|
||||
|
||||
// Oathkeeper 상태를 주기적으로 확인해 다운을 감지합니다.
|
||||
var oathkeeperProbe *HTTPProbe
|
||||
if strings.ToLower(getEnv("OATHKEEPER_HEALTH_ENABLED", "true")) != "false" {
|
||||
intervalSec, err := strconv.Atoi(getEnv("OATHKEEPER_HEALTH_INTERVAL_SECONDS", "10"))
|
||||
if err != nil || intervalSec <= 0 {
|
||||
intervalSec = 10
|
||||
}
|
||||
timeoutSec, err := strconv.Atoi(getEnv("OATHKEEPER_HEALTH_TIMEOUT_SECONDS", "2"))
|
||||
if err != nil || timeoutSec <= 0 {
|
||||
timeoutSec = 2
|
||||
}
|
||||
oathkeeperProbe = NewHTTPProbe(
|
||||
"oathkeeper",
|
||||
getEnv("OATHKEEPER_HEALTH_URL", "http://oathkeeper:4456/health/ready"),
|
||||
time.Duration(intervalSec)*time.Second,
|
||||
time.Duration(timeoutSec)*time.Second,
|
||||
)
|
||||
oathkeeperProbe.Start()
|
||||
} else {
|
||||
slog.Info("Oathkeeper probe disabled")
|
||||
}
|
||||
|
||||
// 2. Initialize Handlers
|
||||
auditHandler := handler.NewAuditHandler(auditRepo)
|
||||
authHandler := handler.NewAuthHandler(redisService, idpProvider)
|
||||
@@ -321,6 +343,32 @@ func main() {
|
||||
status = "degraded"
|
||||
}
|
||||
|
||||
// Check Oathkeeper
|
||||
if oathkeeperProbe != nil {
|
||||
snapshot := oathkeeperProbe.Snapshot()
|
||||
switch snapshot.Status {
|
||||
case "ok":
|
||||
checks["oathkeeper"] = "ok"
|
||||
case "":
|
||||
checks["oathkeeper"] = "unknown"
|
||||
if status != "error" {
|
||||
status = "degraded"
|
||||
}
|
||||
default:
|
||||
if snapshot.Error == "" {
|
||||
checks["oathkeeper"] = "error"
|
||||
} else {
|
||||
checks["oathkeeper"] = "error: " + snapshot.Error
|
||||
}
|
||||
status = "error"
|
||||
}
|
||||
} else {
|
||||
checks["oathkeeper"] = "disabled"
|
||||
if status != "error" {
|
||||
status = "degraded"
|
||||
}
|
||||
}
|
||||
|
||||
if status == "error" {
|
||||
return c.Status(fiber.StatusServiceUnavailable).JSON(fiber.Map{
|
||||
"status": status,
|
||||
|
||||
Reference in New Issue
Block a user