Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion k8s/exceptionless/templates/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ spec:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }}
spec:
# SSE connections are long-lived; give the pod enough time to drain before SIGTERM.
# The preStop sleep lets the ALB/ingress controller deregister the pod before traffic stops,
# then the remaining window allows ASP.NET Core to cancel RequestAborted tokens and clean up.
# When push is eventually enabled behind a Gateway API RoutePolicy, revisit this value.
terminationGracePeriodSeconds: 60
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
Expand All @@ -39,6 +44,13 @@ spec:
- name: {{ template "exceptionless.name" . }}-api
image: "{{ .Values.api.image.repository }}:{{ .Values.version }}"
imagePullPolicy: {{ .Values.api.image.pullPolicy }}
lifecycle:
preStop:
# Give the ALB ~15s to deregister this pod before SIGTERM fires.
# The total graceful window is terminationGracePeriodSeconds (60s) minus
# this sleep, leaving ~45s for ASP.NET Core to drain active SSE connections.
exec:
command: ["sleep", "15"]
livenessProbe:
httpGet:
path: /health
Expand Down Expand Up @@ -82,7 +94,10 @@ spec:
{{- include "exceptionless.otel-env" . | indent 12 }}
- name: RunJobsInProcess
value: 'false'
- name: EnableWebSockets
# SSE rollout prerequisite: Azure Application Gateway for Containers Ingress API
# does not support the routeTimeout=0s override required for long-lived SSE streams.
# Keep push disabled here until this route moves to Gateway API + RoutePolicy.
- name: EnablePush
value: 'false'
{{- if (empty .Values.storage.connectionString) }}
volumeMounts:
Expand Down Expand Up @@ -163,6 +178,8 @@ metadata:
alb.networking.azure.io/alb-namespace: {{ .Values.ingress.albNamespace }}
alb.networking.azure.io/alb-frontend: {{ template "exceptionless.fullname" . }}-fe
cert-manager.io/cluster-issuer: {{ .Values.ingress.clusterIssuer }}
# SSE is not safe to enable behind the current AGC Ingress API path.
# Migrate to Gateway API and attach a RoutePolicy with routeTimeout: 0s before enabling push.
spec:
ingressClassName: azure-alb-external
tls:
Expand Down
4 changes: 2 additions & 2 deletions src/Exceptionless.Core/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,8 @@ public static void LogConfiguration(IServiceProvider serviceProvider, AppOptions
if (String.IsNullOrEmpty(appOptions.StorageOptions.Provider))
logger.LogWarning("Distributed storage is NOT enabled on {MachineName}", Environment.MachineName);

if (!appOptions.EnableWebSockets)
logger.LogWarning("Web Sockets is NOT enabled on {MachineName}", Environment.MachineName);
if (!appOptions.EnablePush)
logger.LogWarning("Real-time push (SSE) is NOT enabled on {MachineName}", Environment.MachineName);

if (String.IsNullOrEmpty(appOptions.EmailOptions.SmtpHost))
logger.LogWarning("Emails will NOT be sent until the SmtpHost is configured on {MachineName}", Environment.MachineName);
Expand Down
9 changes: 7 additions & 2 deletions src/Exceptionless.Core/Configuration/AppOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ public class AppOptions

public bool EnableRepositoryNotifications { get; internal set; }

public bool EnableWebSockets { get; internal set; }
/// <summary>
/// Controls whether real-time push (SSE) is enabled. Reads from either 'EnablePush'
/// or legacy 'EnableWebSockets' config key for backward compatibility.
/// </summary>
public bool EnablePush { get; internal set; }

public string? Version { get; internal set; }

Expand Down Expand Up @@ -111,7 +115,8 @@ public static AppOptions ReadFromConfiguration(IConfiguration config)
options.BulkBatchSize = config.GetValue(nameof(options.BulkBatchSize), 1000);

options.EnableRepositoryNotifications = config.GetValue(nameof(options.EnableRepositoryNotifications), true);
options.EnableWebSockets = config.GetValue(nameof(options.EnableWebSockets), true);
// Support both new 'EnablePush' and legacy 'EnableWebSockets' config keys
options.EnablePush = config.GetValue(nameof(options.EnablePush), config.GetValue("EnableWebSockets", true));
Comment thread
niemyjski marked this conversation as resolved.

try
{
Expand Down
5 changes: 5 additions & 0 deletions src/Exceptionless.Core/Utility/AppDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ public GaugeInfo(Meter meter, string name)

internal static readonly Counter<int> SavedViewsSize = Meter.CreateCounter<int>("ex.savedviews.size", description: "Size of user saved views");
internal static readonly Counter<int> SavedViewsViewTypeSize = Meter.CreateCounter<int>("ex.savedviews.viewtype.size", description: "Size of user saved views by view type");

internal static readonly Counter<int> PushSseConnectionsOpened = Meter.CreateCounter<int>("ex.push.connections.sse.opened", description: "SSE push connections opened");
internal static readonly Counter<int> PushSseConnectionsClosed = Meter.CreateCounter<int>("ex.push.connections.sse.closed", description: "SSE push connections closed");
internal static readonly Counter<int> PushWebSocketConnectionsOpened = Meter.CreateCounter<int>("ex.push.connections.websocket.opened", description: "WebSocket push connections opened");
internal static readonly Counter<int> PushWebSocketConnectionsClosed = Meter.CreateCounter<int>("ex.push.connections.websocket.closed", description: "WebSocket push connections closed");
}

public static class MetricsClientExtensions
Expand Down
1 change: 1 addition & 0 deletions src/Exceptionless.Web/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class Bootstrapper
{
public static void RegisterServices(IServiceCollection services, AppOptions appOptions, ILoggerFactory loggerFactory)
{
services.AddSingleton<SseConnectionManager>();
services.AddSingleton<WebSocketConnectionManager>();
services.AddSingleton<MessageBusBroker>();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(function () {
"use strict";

// Deprecated: keep the legacy Angular client on WebSocket during the SSE rollout.
angular
.module("exceptionless.websocket", ["app.config", "exceptionless", "exceptionless.auth"])
.factory("websocketService", function ($ExceptionlessClient, $rootScope, $timeout, authService, BASE_URL) {
Expand Down
Loading
Loading