diff --git a/api/v1alpha1/envoygateway_metrics_types.go b/api/v1alpha1/envoygateway_metrics_types.go index 0b827a06afa..7d796e848f6 100644 --- a/api/v1alpha1/envoygateway_metrics_types.go +++ b/api/v1alpha1/envoygateway_metrics_types.go @@ -5,6 +5,8 @@ package v1alpha1 +import gatewayv1 "sigs.k8s.io/gateway-api/apis/v1" + // EnvoyGatewayMetrics defines control plane push/pull metrics configurations. type EnvoyGatewayMetrics struct { // Sinks defines the metric sinks where metrics are sent to. @@ -38,6 +40,18 @@ type EnvoyGatewayOpenTelemetrySink struct { // +kubebuilder:validation:Minimum=0 // +kubebuilder:default=4317 Port int32 `json:"port,omitempty"` + // ExportInterval configures the intervening time between exports for a + // Sink. This option overrides any value set for the + // OTEL_METRIC_EXPORT_INTERVAL environment variable. + // If ExportInterval is less than or equal to zero, 60 seconds + // is used as the default. + ExportInterval *gatewayv1.Duration `json:"exportInterval,omitempty"` + // ExportTimeout configures the time a Sink waits for an export to + // complete before canceling it. This option overrides any value set for the + // OTEL_METRIC_EXPORT_TIMEOUT environment variable. + // If ExportTimeout is less than or equal to zero, 30 seconds + // is used as the default. + ExportTimeout *gatewayv1.Duration `json:"exportTimeout,omitempty"` } // EnvoyGatewayPrometheusProvider will expose prometheus endpoint in pull mode. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fd607c16af7..0dbd0c9c052 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1307,7 +1307,7 @@ func (in *EnvoyGatewayMetricSink) DeepCopyInto(out *EnvoyGatewayMetricSink) { if in.OpenTelemetry != nil { in, out := &in.OpenTelemetry, &out.OpenTelemetry *out = new(EnvoyGatewayOpenTelemetrySink) - **out = **in + (*in).DeepCopyInto(*out) } } @@ -1351,6 +1351,16 @@ func (in *EnvoyGatewayMetrics) DeepCopy() *EnvoyGatewayMetrics { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvoyGatewayOpenTelemetrySink) DeepCopyInto(out *EnvoyGatewayOpenTelemetrySink) { *out = *in + if in.ExportInterval != nil { + in, out := &in.ExportInterval, &out.ExportInterval + *out = new(apisv1.Duration) + **out = **in + } + if in.ExportTimeout != nil { + in, out := &in.ExportTimeout, &out.ExportTimeout + *out = new(apisv1.Duration) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyGatewayOpenTelemetrySink. diff --git a/internal/metrics/register.go b/internal/metrics/register.go index e6332aeb8c9..a73d433718b 100644 --- a/internal/metrics/register.go +++ b/internal/metrics/register.go @@ -31,7 +31,11 @@ const ( // Init initializes and registers the global metrics server. func Init(cfg *config.Server) error { - options := newOptions(cfg) + options, err := newOptions(cfg) + if err != nil { + return err + } + handler, err := registerForHandler(options) if err != nil { return err @@ -71,7 +75,7 @@ func start(address string, handler http.Handler) error { return nil } -func newOptions(svr *config.Server) registerOptions { +func newOptions(svr *config.Server) (registerOptions, error) { newOpts := registerOptions{} newOpts.address = net.JoinHostPort(v1alpha1.GatewayMetricsHost, fmt.Sprint(v1alpha1.GatewayMetricsPort)) @@ -84,14 +88,37 @@ func newOptions(svr *config.Server) registerOptions { } for _, config := range svr.EnvoyGateway.GetEnvoyGatewayTelemetry().Metrics.Sinks { - newOpts.pushOptions.sinks = append(newOpts.pushOptions.sinks, metricsSink{ + sink := metricsSink{ host: config.OpenTelemetry.Host, port: config.OpenTelemetry.Port, protocol: config.OpenTelemetry.Protocol, - }) + } + + // we do not explicitly set default values for ExporterInterval and ExporterTimeout + // instead, let the upstream repository set default values for it + if config.OpenTelemetry.ExportInterval != nil && len(*config.OpenTelemetry.ExportInterval) != 0 { + interval, err := time.ParseDuration(string(*config.OpenTelemetry.ExportInterval)) + if err != nil { + metricsLogger.Error(err, "failed to parse exporter interval time format") + return newOpts, err + } + + sink.exportInterval = interval + } + if config.OpenTelemetry.ExportTimeout != nil && len(*config.OpenTelemetry.ExportTimeout) != 0 { + timeout, err := time.ParseDuration(string(*config.OpenTelemetry.ExportTimeout)) + if err != nil { + metricsLogger.Error(err, "failed to parse exporter timeout time format") + return newOpts, err + } + + sink.exportTimeout = timeout + } + + newOpts.pushOptions.sinks = append(newOpts.pushOptions.sinks, sink) } - return newOpts + return newOpts, nil } // registerForHandler sets the global metrics registry to the provided Prometheus registerer. @@ -155,7 +182,17 @@ func registerOTELHTTPexporter(otelOpts *[]metric.Option, opts registerOptions) e return err } - otelreader := metric.NewPeriodicReader(httpexporter) + periodOpts := []metric.PeriodicReaderOption{} + // If we do not set the interval or timeout for the exporter, + // we let the upstream set the default value for it. + if sink.exportInterval != 0 { + periodOpts = append(periodOpts, metric.WithInterval(sink.exportInterval)) + } + if sink.exportTimeout != 0 { + periodOpts = append(periodOpts, metric.WithTimeout(sink.exportTimeout)) + } + + otelreader := metric.NewPeriodicReader(httpexporter, periodOpts...) *otelOpts = append(*otelOpts, metric.WithReader(otelreader)) metricsLogger.Info("initialized otel http metrics push endpoint", "address", address) } @@ -178,7 +215,17 @@ func registerOTELgRPCexporter(otelOpts *[]metric.Option, opts registerOptions) e return err } - otelreader := metric.NewPeriodicReader(httpexporter) + periodOpts := []metric.PeriodicReaderOption{} + // If we do not set the interval or timeout for the exporter, + // we let the upstream set the default value for it. + if sink.exportInterval != 0 { + periodOpts = append(periodOpts, metric.WithInterval(sink.exportInterval)) + } + if sink.exportTimeout != 0 { + periodOpts = append(periodOpts, metric.WithTimeout(sink.exportTimeout)) + } + + otelreader := metric.NewPeriodicReader(httpexporter, periodOpts...) *otelOpts = append(*otelOpts, metric.WithReader(otelreader)) metricsLogger.Info("initialized otel grpc metrics push endpoint", "address", address) } @@ -200,7 +247,9 @@ type registerOptions struct { } type metricsSink struct { - protocol string - host string - port int32 + protocol string + host string + port int32 + exportTimeout time.Duration + exportInterval time.Duration } diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 6ac4fe6b123..85280beb0f5 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -1034,6 +1034,8 @@ _Appears in:_ | `host` | _string_ | true | Host define the sink service hostname. | | `protocol` | _string_ | true | Protocol define the sink service protocol. | | `port` | _integer_ | false | Port defines the port the sink service is exposed on. | +| `exportInterval` | _[Duration](#duration)_ | true | ExportInterval configures the intervening time between exports for a
Sink. This option overrides any value set for the
OTEL_METRIC_EXPORT_INTERVAL environment variable.
If ExportInterval is less than or equal to zero, 60 seconds
is used as the default. | +| `exportTimeout` | _[Duration](#duration)_ | true | ExportTimeout configures the time a Sink waits for an export to
complete before canceling it. This option overrides any value set for the
OTEL_METRIC_EXPORT_TIMEOUT environment variable.
If ExportTimeout is less than or equal to zero, 30 seconds
is used as the default. | #### EnvoyGatewayPrometheusProvider