feat: Enhance telemetry services with Kubernetes cluster auto-discovery and health check configuration

This commit is contained in:
Nawaz Dhandala
2026-03-18 11:20:13 +00:00
parent 4cba330605
commit e4a76117b1
7 changed files with 244 additions and 40 deletions

View File

@@ -36,7 +36,9 @@ type Language =
| "ruby"
| "elixir"
| "cpp"
| "swift";
| "swift"
| "react"
| "angular";
interface LanguageOption {
key: Language;
@@ -56,6 +58,8 @@ const languages: Array<LanguageOption> = [
{ key: "elixir", label: "Elixir", shortLabel: "Elixir" },
{ key: "cpp", label: "C++", shortLabel: "C++" },
{ key: "swift", label: "Swift", shortLabel: "Swift" },
{ key: "react", label: "React (Browser)", shortLabel: "React" },
{ key: "angular", label: "Angular (Browser)", shortLabel: "Angular" },
];
type IntegrationMethod = "opentelemetry" | "fluentbit" | "fluentd";
@@ -99,7 +103,7 @@ function getOtelInstallSnippet(lang: Language): {
return {
code: `pip install opentelemetry-api \\
opentelemetry-sdk \\
opentelemetry-exporter-otlp-proto-grpc \\
opentelemetry-exporter-otlp-proto-http \\
opentelemetry-instrumentation`,
language: "bash",
};
@@ -107,9 +111,9 @@ function getOtelInstallSnippet(lang: Language): {
return {
code: `go get go.opentelemetry.io/otel \\
go.opentelemetry.io/otel/sdk \\
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc \\
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc \\
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc`,
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp \\
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp \\
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`,
language: "bash",
};
case "java":
@@ -200,6 +204,30 @@ vcpkg install opentelemetry-cpp[otlp-grpc]
.product(name: "OtlpGRPCSpanExporting", package: "opentelemetry-swift"),`,
language: "bash",
};
case "react":
return {
code: `npm install @opentelemetry/api \\
@opentelemetry/sdk-trace-web \\
@opentelemetry/sdk-trace-base \\
@opentelemetry/exporter-trace-otlp-http \\
@opentelemetry/instrumentation-document-load \\
@opentelemetry/instrumentation-fetch \\
@opentelemetry/instrumentation-xml-http-request \\
@opentelemetry/context-zone`,
language: "bash",
};
case "angular":
return {
code: `npm install @opentelemetry/api \\
@opentelemetry/sdk-trace-web \\
@opentelemetry/sdk-trace-base \\
@opentelemetry/exporter-trace-otlp-http \\
@opentelemetry/instrumentation-document-load \\
@opentelemetry/instrumentation-fetch \\
@opentelemetry/instrumentation-xml-http-request \\
@opentelemetry/context-zone`,
language: "bash",
};
}
}
@@ -253,8 +281,8 @@ from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter
from opentelemetry.sdk.resources import Resource
resource = Resource.create({"service.name": "my-service"})
@@ -288,23 +316,20 @@ metrics.set_meter_provider(MeterProvider(resource=resource, metric_readers=[metr
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
)
func initTracer() (*sdktrace.TracerProvider, error) {
ctx := metadata.AppendToOutgoingContext(
context.Background(),
"x-oneuptime-token", "<YOUR_ONEUPTIME_TOKEN>",
)
ctx := context.Background()
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("<YOUR_ONEUPTIME_OTLP_HOST>"),
otlptracegrpc.WithTLSCredentials(insecure.NewCredentials()),
exporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("<YOUR_ONEUPTIME_OTLP_HOST>"),
otlptracehttp.WithHeaders(map[string]string{
"x-oneuptime-token": "<YOUR_ONEUPTIME_TOKEN>",
}),
)
if err != nil {
return nil, err
@@ -551,6 +576,106 @@ func initTracer() {
}`,
language: "swift",
};
case "react":
return {
code: `// src/tracing.ts - Import this file in your index.tsx before ReactDOM.render()
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { ZoneContextManager } from '@opentelemetry/context-zone';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';
import { Resource } from '@opentelemetry/resources';
const provider = new WebTracerProvider({
resource: new Resource({
'service.name': 'my-react-app',
}),
});
provider.addSpanProcessor(
new BatchSpanProcessor(
new OTLPTraceExporter({
url: '<YOUR_ONEUPTIME_OTLP_URL>/v1/traces',
headers: { 'x-oneuptime-token': '<YOUR_ONEUPTIME_TOKEN>' },
})
)
);
provider.register({
contextManager: new ZoneContextManager(),
});
registerInstrumentations({
instrumentations: [
new DocumentLoadInstrumentation(),
new FetchInstrumentation({
propagateTraceHeaderCorsUrls: [/.*/],
}),
new XMLHttpRequestInstrumentation({
propagateTraceHeaderCorsUrls: [/.*/],
}),
],
});
// In index.tsx:
// import './tracing'; // Must be first import
// import React from 'react';
// ...`,
language: "typescript",
};
case "angular":
return {
code: `// src/tracing.ts
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import { BatchSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { ZoneContextManager } from '@opentelemetry/context-zone';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { DocumentLoadInstrumentation } from '@opentelemetry/instrumentation-document-load';
import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch';
import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request';
import { Resource } from '@opentelemetry/resources';
const provider = new WebTracerProvider({
resource: new Resource({
'service.name': 'my-angular-app',
}),
});
provider.addSpanProcessor(
new BatchSpanProcessor(
new OTLPTraceExporter({
url: '<YOUR_ONEUPTIME_OTLP_URL>/v1/traces',
headers: { 'x-oneuptime-token': '<YOUR_ONEUPTIME_TOKEN>' },
})
)
);
provider.register({
contextManager: new ZoneContextManager(),
});
registerInstrumentations({
instrumentations: [
new DocumentLoadInstrumentation(),
new FetchInstrumentation({
propagateTraceHeaderCorsUrls: [/.*/],
}),
new XMLHttpRequestInstrumentation({
propagateTraceHeaderCorsUrls: [/.*/],
}),
],
});
// In main.ts, import before bootstrapping:
// import './tracing'; // Must be first import
// import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
// ...`,
language: "typescript",
};
}
}

View File

@@ -8,6 +8,10 @@ metadata:
{{- include "kubernetes-agent.labels" . | nindent 4 }}
data:
otel-collector-config.yaml: |
extensions:
health_check:
endpoint: "0.0.0.0:13133"
receivers:
# Collect pod logs from /var/log/pods
filelog:
@@ -115,11 +119,13 @@ data:
exporters:
otlphttp:
endpoint: "{{ .Values.oneuptime.url }}"
endpoint: "{{ .Values.oneuptime.url }}/otlp"
headers:
x-oneuptime-token: "${env:ONEUPTIME_API_KEY}"
service:
extensions:
- health_check
pipelines:
logs:
receivers:

View File

@@ -7,6 +7,10 @@ metadata:
{{- include "kubernetes-agent.labels" . | nindent 4 }}
data:
otel-collector-config.yaml: |
extensions:
health_check:
endpoint: "0.0.0.0:13133"
receivers:
# Collect node, pod, and container resource metrics from kubelet
kubeletstats:
@@ -133,22 +137,25 @@ data:
# Batch telemetry for efficient export
batch:
send_batch_size: 1024
send_batch_size: 200
send_batch_max_size: 500
timeout: 10s
# Limit memory usage
memory_limiter:
check_interval: 5s
limit_mib: 400
spike_limit_mib: 100
limit_mib: 1500
spike_limit_mib: 300
exporters:
otlphttp:
endpoint: "{{ .Values.oneuptime.url }}"
endpoint: "{{ .Values.oneuptime.url }}/otlp"
headers:
x-oneuptime-token: "${env:ONEUPTIME_API_KEY}"
service:
extensions:
- health_check
pipelines:
metrics:
receivers:

View File

@@ -29,11 +29,11 @@ deployment:
replicas: 1
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
# Control plane monitoring (etcd, API server, scheduler, controller manager)
# Disabled by default — enable for self-managed clusters.

View File

@@ -1,6 +1,9 @@
import { ExpressRequest } from "Common/Server/Utils/Express";
import CaptureSpan from "Common/Server/Utils/Telemetry/CaptureSpan";
import { JSONArray, JSONObject } from "Common/Types/JSON";
import ObjectID from "Common/Types/ObjectID";
import KubernetesClusterService from "Common/Server/Services/KubernetesClusterService";
import logger from "Common/Server/Utils/Logger";
export default abstract class OtelIngestBaseService {
@CaptureSpan()
@@ -33,6 +36,57 @@ export default abstract class OtelIngestBaseService {
return "Unknown Service";
}
@CaptureSpan()
protected static getClusterNameFromAttributes(
attributes: JSONArray,
): string | null {
for (const attribute of attributes) {
if (
attribute["key"] === "k8s.cluster.name" &&
attribute["value"] &&
(attribute["value"] as JSONObject)["stringValue"]
) {
const value = (attribute["value"] as JSONObject)["stringValue"];
if (typeof value === "string" && value.trim()) {
return value.trim();
}
}
}
return null;
}
@CaptureSpan()
protected static async autoDiscoverKubernetesCluster(data: {
projectId: ObjectID;
attributes: JSONArray;
}): Promise<void> {
try {
const clusterName: string | null = this.getClusterNameFromAttributes(
data.attributes,
);
if (!clusterName) {
return;
}
const cluster =
await KubernetesClusterService.findOrCreateByClusterIdentifier({
projectId: data.projectId,
clusterIdentifier: clusterName,
});
if (cluster._id) {
await KubernetesClusterService.updateLastSeen(
new ObjectID(cluster._id.toString()),
);
}
} catch (err) {
logger.error(
"Error auto-discovering Kubernetes cluster: " + (err as Error).message,
);
}
}
@CaptureSpan()
protected static getServiceNameFromHeaders(
req: ExpressRequest,

View File

@@ -120,13 +120,22 @@ export default class OtelLogsIngestService extends OtelIngestBaseService {
await Promise.resolve();
}
resourceLogCounter++;
const serviceName: string = this.getServiceNameFromAttributes(
req,
const resourceAttributes_raw: JSONArray =
((resourceLog["resource"] as JSONObject)?.[
"attributes"
] as JSONArray) || [],
] as JSONArray) || [];
const serviceName: string = this.getServiceNameFromAttributes(
req,
resourceAttributes_raw,
);
// Auto-discover Kubernetes cluster from resource attributes
await this.autoDiscoverKubernetesCluster({
projectId,
attributes: resourceAttributes_raw,
});
if (!serviceDictionary[serviceName]) {
const service: {
serviceId: ObjectID;
@@ -151,10 +160,7 @@ export default class OtelLogsIngestService extends OtelIngestBaseService {
serviceName: serviceName,
}),
...TelemetryUtil.getAttributes({
items:
((resourceLog["resource"] as JSONObject)?.[
"attributes"
] as JSONArray) || [],
items: resourceAttributes_raw,
prefixKeysWithString: "resource",
}),
};

View File

@@ -118,13 +118,22 @@ export default class OtelMetricsIngestService extends OtelIngestBaseService {
await Promise.resolve();
}
resourceMetricCounter++;
const serviceName: string = this.getServiceNameFromAttributes(
req,
const resourceAttributes_raw: JSONArray =
((resourceMetric["resource"] as JSONObject)?.[
"attributes"
] as JSONArray) || [],
] as JSONArray) || [];
const serviceName: string = this.getServiceNameFromAttributes(
req,
resourceAttributes_raw,
);
// Auto-discover Kubernetes cluster from resource attributes
await this.autoDiscoverKubernetesCluster({
projectId,
attributes: resourceAttributes_raw,
});
if (!serviceDictionary[serviceName]) {
const service: {
serviceId: ObjectID;
@@ -152,10 +161,7 @@ export default class OtelMetricsIngestService extends OtelIngestBaseService {
serviceName: serviceName,
}),
...TelemetryUtil.getAttributes({
items:
((resourceMetric["resource"] as JSONObject)?.[
"attributes"
] as JSONArray) || [],
items: resourceAttributes_raw,
prefixKeysWithString: "resource",
}),
};