2026-04-07 11:48:19 +08:00

97 lines
2.6 KiB
Go

package service
import (
"context"
"fmt"
"net/http"
"time"
"gorm.io/gorm"
"app-deploy-platform/backend/internal/model"
)
func (m *Manager) StartHealthProbe(ctx context.Context) {
ticker := time.NewTicker(m.cfg.HealthcheckIntervalDuration())
defer ticker.Stop()
m.probeInstances(ctx)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
m.probeInstances(ctx)
}
}
}
func (m *Manager) probeInstances(ctx context.Context) {
var instances []model.ServiceInstance
if err := m.db.WithContext(ctx).Preload("Host").Find(&instances).Error; err != nil {
return
}
client := &http.Client{Timeout: 3 * time.Second}
now := time.Now()
for _, instance := range instances {
healthStatus := probeURL(ctx, client, buildURL(instance.Host.PrivateIP, instance.Port, instance.HealthPath))
readyStatus := probeURL(ctx, client, buildURL(instance.Host.PrivateIP, instance.Port, instance.ReadyPath))
updates := map[string]any{
"health_status": healthStatus,
"ready_status": readyStatus,
"last_health_checked_at": &now,
"updated_at": now,
}
if err := m.db.WithContext(ctx).Model(&model.ServiceInstance{}).
Where("id = ?", instance.ID).
Updates(updates).Error; err != nil {
continue
}
hostUpdates := map[string]any{"updated_at": now}
if healthStatus == model.StatusUp || readyStatus == model.StatusUp {
hostUpdates["last_seen_at"] = &now
}
_ = m.db.WithContext(ctx).Model(&model.Host{}).Where("id = ?", instance.HostID).Updates(hostUpdates).Error
if m.redis != nil {
cacheValue := fmt.Sprintf(`{"health":"%s","ready":"%s","checked_at":"%s"}`, healthStatus, readyStatus, now.Format(time.RFC3339))
_ = m.redis.Set(ctx, fmt.Sprintf("instance:%d:health", instance.ID), cacheValue, m.cfg.HealthcheckIntervalDuration()*2).Err()
}
}
}
func buildURL(host string, port int, path string) string {
if path == "" {
path = "/health"
}
return fmt.Sprintf("http://%s:%d%s", host, port, path)
}
func probeURL(ctx context.Context, client *http.Client, url string) string {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return model.StatusUnknown
}
resp, err := client.Do(req)
if err != nil {
return model.StatusDown
}
defer resp.Body.Close()
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusBadRequest {
return model.StatusUp
}
return model.StatusDown
}
func updateDeploymentStatus(tx *gorm.DB, deploymentID uint, values map[string]any) error {
return tx.Model(&model.Deployment{}).Where("id = ?", deploymentID).Updates(values).Error
}