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 }