Skip to content

Centralized Version Aggregation Service

Overview

This document outlines the planned architecture for centralizing service version information through the API service, eliminating the need for public ingresses on internal services.

Current Architecture (Temporary)

Currently, the web application fetches version information directly from multiple backend services:

┌─────────────────┐
│  Web Frontend   │
└────────┬────────┘
    ┌────┴────┬────────────┬────────────┐
    ▼         ▼            ▼            ▼
┌───────┐ ┌───────┐ ┌────────────┐ ┌────────┐
│  API  │ │  PM   │ │   Quartz   │ │ Lambda │
│/health│ │/health│ │  /health   │ │ (AWS)  │
└───────┘ └───────┘ └────────────┘ └────────┘
    │         │            │            │
    ▼         ▼            ▼            ▼
  Public   Public       Public     Version in
 Ingress   Ingress      Ingress    config only

Current Implementation Notes: - API, PM, and Quartz all expose /health/live endpoints publicly - Web app makes 3 parallel HTTP requests to fetch version info - S3 Notifier version comes from static configuration (Lambda has no health endpoint) - Each service maintains its own GitVersion information

Why Public Ingresses Were Added (Temporary): - Quick solution to display version info in version dialog - Allows debugging in staging/production environments - Enables health monitoring per-service

Target Architecture (Planned)

Centralize all version information through the API service:

┌─────────────────┐
│  Web Frontend   │
└────────┬────────┘
         ▼ HTTP + SignalR
    ┌─────────┐
    │   API   │  ◄── Public Ingress (single entry point)
    │/version │
    └────┬────┘
    ┌────┴────┬────────────┐
    ▼         ▼            ▼
┌───────┐ ┌───────┐ ┌────────────┐
│  PM   │ │Quartz │ │  Lambda    │
│(K8s)  │ │(K8s)  │ │  (AWS)     │
└───────┘ └───────┘ └────────────┘
    │         │            │
    ▼         ▼            ▼
 Internal  Internal     Version
 Service   Service      via config

Key Changes

  1. Remove Public Ingresses: Disable public access to PM and Quartz
  2. API Version Endpoint: New /api/version/services endpoint
  3. SignalR Integration: Real-time version updates via existing SignalR hub
  4. MassTransit Health Checks: Periodic internal health aggregation

Implementation Plan

Phase 1: API Version Endpoint

New Endpoint: GET /api/version/services

public class ServicesVersionResponse
{
    public ServiceVersion Api { get; set; }
    public ServiceVersion ProjectManagement { get; set; }
    public ServiceVersion Quartz { get; set; }
    public ServiceVersion S3Notifier { get; set; }
    public DateTime Timestamp { get; set; }
}

public class ServiceVersion
{
    public string Name { get; set; }
    public string Version { get; set; }
    public string Status { get; set; }  // healthy, unhealthy, unavailable
    public string Sha { get; set; }
    public string HealthStatus { get; set; }
}

Implementation Location: src/services/api/SyRF.API.Endpoint/Controllers/VersionController.cs

Phase 2: Internal Health Aggregation

MassTransit Health Consumer:

public class HealthAggregationService : IHostedService
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IOptions<ServicesConfiguration> _config;
    private readonly IHubContext<SyrfHub> _hubContext;

    // Poll internal services every 30 seconds
    // Compare with previous state
    // Broadcast changes via SignalR
}

Internal Service Discovery: - PM: http://project-management.{namespace}.svc.cluster.local:8081/health/live - Quartz: http://quartz.{namespace}.svc.cluster.local:8082/health/live - S3 Notifier: Version from configuration (no runtime check)

Phase 3: SignalR Integration

Hub Method:

public class SyrfHub : Hub
{
    // Existing hub methods...

    // New: Called by HealthAggregationService when versions change
    public async Task BroadcastServiceVersionsChanged(ServicesVersionResponse versions)
    {
        await Clients.All.SendAsync("ServiceVersionsChanged", versions);
    }
}

Web Frontend Changes:

// version-info-dialog.component.ts
private setupSignalRSubscription(): void {
    this.signalRService.on<ServicesVersionResponse>('ServiceVersionsChanged', (versions) => {
        this.updateVersionsFromSignalR(versions);
    });
}

Phase 4: Remove Public Ingresses

cluster-gitops Changes:

# syrf/environments/staging/quartz/values.yaml
ingress:
  enabled: false  # Changed from true

# syrf/environments/production/quartz/values.yaml
ingress:
  enabled: false  # Changed from true

Web Configuration Changes:

# syrf/environments/staging/web/values.yaml
quartz:
  origin: ""  # No longer needed - API aggregates

pm:
  origin: ""  # No longer needed - API aggregates

Benefits

  1. Security: Reduces attack surface by removing public endpoints
  2. Simplicity: Single entry point for version information
  3. Real-time Updates: SignalR provides instant notifications of version changes
  4. Consistency: Version info comes from authoritative source (API)
  5. Cost: Fewer ingress resources and TLS certificates

Migration Path

  1. Current State: Public ingresses enabled (temporary)
  2. Implement API endpoint: Add /api/version/services to API
  3. Update Web: Switch version dialog to use new API endpoint
  4. Add SignalR: Implement real-time version notifications
  5. Remove Ingresses: Disable public access to PM/Quartz
  6. Clean up config: Remove unused origin configuration

Security Considerations

  • Internal service discovery uses Kubernetes DNS
  • No credentials needed for internal health checks
  • Version information is not sensitive (already public in UI)
  • SignalR already uses existing authentication

Configuration

Services Configuration (API appsettings):

{
  "Services": {
    "ProjectManagement": {
      "InternalUrl": "http://project-management:8081"
    },
    "Quartz": {
      "InternalUrl": "http://quartz:8082"
    },
    "S3Notifier": {
      "Version": "${S3NotifierVersion}"
    }
  },
  "HealthAggregation": {
    "PollIntervalSeconds": 30,
    "TimeoutSeconds": 5
  }
}

Timeline

Phase Description Estimated Effort
1 API Version Endpoint 2-3 hours
2 Internal Health Aggregation 3-4 hours
3 SignalR Integration 2-3 hours
4 Web Frontend Changes 2-3 hours
5 Remove Ingresses + Config 1 hour

Total Estimated Effort: 10-14 hours

Open Questions

  1. Should we cache version info in Redis for faster responses?
  2. How should we handle version info when services are starting up?
  3. Should the health aggregation run on all API replicas or just one?