mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
feat: Enhance telemetry services with Kubernetes cluster auto-discovery and health check configuration
This commit is contained in:
@@ -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",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
}),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user