Skip to content
Merged
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
10 changes: 10 additions & 0 deletions api/v1alpha1/vector_common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ type VectorCommon struct {
// Enable internal metrics exporter
// +optional
InternalMetrics bool `json:"internalMetrics,omitempty"`
// ScrapeInterval defines the interval at which Prometheus should scrape metrics.
// Example values: "30s", "1m", "5m". If not specified, Prometheus default is used.
// +optional
// +kubebuilder:validation:Pattern=`^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$`
ScrapeInterval string `json:"scrapeInterval,omitempty"`
// ScrapeTimeout defines the timeout for scraping metrics.
// Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used.
// +optional
// +kubebuilder:validation:Pattern=`^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$`
ScrapeTimeout string `json:"scrapeTimeout,omitempty"`
// List of volumes that can be mounted by containers belonging to the pod.
// +optional
Volumes []v1.Volume `json:"volumes,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3147,6 +3147,18 @@ spec:
schedulerName:
description: SchedulerName - defines kubernetes scheduler name
type: string
scrapeInterval:
description: |-
ScrapeInterval defines the interval at which Prometheus should scrape metrics.
Example values: "30s", "1m", "5m". If not specified, Prometheus default is used.
pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$
type: string
scrapeTimeout:
description: |-
ScrapeTimeout defines the timeout for scraping metrics.
Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used.
pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$
type: string
selector:
description: |-
Selector defines a filter for the Vector Pipeline and Cluster Vector Pipeline by labels.
Expand Down
12 changes: 12 additions & 0 deletions config/crd/bases/observability.kaasops.io_vectoraggregators.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3141,6 +3141,18 @@ spec:
schedulerName:
description: SchedulerName - defines kubernetes scheduler name
type: string
scrapeInterval:
description: |-
ScrapeInterval defines the interval at which Prometheus should scrape metrics.
Example values: "30s", "1m", "5m". If not specified, Prometheus default is used.
pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$
type: string
scrapeTimeout:
description: |-
ScrapeTimeout defines the timeout for scraping metrics.
Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used.
pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$
type: string
selector:
description: |-
Selector defines a filter for the Vector Pipeline and Cluster Vector Pipeline by labels.
Expand Down
12 changes: 12 additions & 0 deletions config/crd/bases/observability.kaasops.io_vectors.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3149,6 +3149,18 @@ spec:
schedulerName:
description: SchedulerName - defines kubernetes scheduler name
type: string
scrapeInterval:
description: |-
ScrapeInterval defines the interval at which Prometheus should scrape metrics.
Example values: "30s", "1m", "5m". If not specified, Prometheus default is used.
pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$
type: string
scrapeTimeout:
description: |-
ScrapeTimeout defines the timeout for scraping metrics.
Example values: "10s", "30s". Must be less than ScrapeInterval. If not specified, Prometheus default is used.
pattern: ^(0|([0-9]+(\.[0-9]+)?(ms|s|m|h))+)$
type: string
tolerations:
description: Tolerations If specified, the pod's tolerations.
items:
Expand Down
14 changes: 13 additions & 1 deletion docs/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# Vector Spec
<table>
<tr>
<td rowspan="24">agent</td>
<td rowspan="27">agent</td>
<td>image</td>
<td>Image for Vector agent. <code>timberio/vector:0.48.0-distroless-libc</code> by default</td>
</tr>
Expand All @@ -26,6 +26,18 @@
<td><a href="https://vector.dev/docs/reference/api/">api</a></td>
<td><a href="https://github.com/kaasops/vector-operator/blob/main/docs/specification.md#api-spec">ApiSpec</a></td>
</tr>
<tr>
<td>internalMetrics</td>
<td>Enable internal metrics exporter. When enabled, a PodMonitor resource is created for Prometheus scraping. By default - <code>false</code></td>
</tr>
<tr>
<td>scrapeInterval</td>
<td>Interval at which Prometheus should scrape metrics from the internal metrics exporter. Examples: <code>"30s"</code>, <code>"1m"</code>, <code>"5m"</code>. Only used when <code>internalMetrics</code> is <code>true</code>. If not specified, Prometheus default is used.</td>
</tr>
<tr>
<td>scrapeTimeout</td>
<td>Timeout for scraping metrics. Must be less than <code>scrapeInterval</code>. Examples: <code>"10s"</code>, <code>"30s"</code>. Only used when <code>internalMetrics</code> is <code>true</code>. If not specified, Prometheus default is used.</td>
</tr>
<tr>
<td>service</td>
<td>Temporary field for enabling service for Vector DaemonSet. By default - <code>false</code></td>
Expand Down
19 changes: 13 additions & 6 deletions internal/vector/aggregator/podmonitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,22 @@ func (ctrl *Controller) createVectorAggregatorPodMonitor() *monitorv1.PodMonitor
matchLabels := ctrl.matchLabelsForVectorAggregator()
annotations := ctrl.annotationsForVectorAggregator()

endpoint := monitorv1.PodMetricsEndpoint{
Path: "/metrics",
Port: "prom-exporter",
}

if ctrl.Spec.ScrapeInterval != "" {
endpoint.Interval = monitorv1.Duration(ctrl.Spec.ScrapeInterval)
}
if ctrl.Spec.ScrapeTimeout != "" {
endpoint.ScrapeTimeout = monitorv1.Duration(ctrl.Spec.ScrapeTimeout)
}

podmonitor := &monitorv1.PodMonitor{
ObjectMeta: ctrl.objectMetaVectorAggregator(labels, annotations, ctrl.Namespace),
Spec: monitorv1.PodMonitorSpec{
PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{
{
Path: "/metrics",
Port: "prom-exporter",
},
},
PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{endpoint},
Selector: metav1.LabelSelector{
MatchLabels: matchLabels,
},
Expand Down
227 changes: 227 additions & 0 deletions internal/vector/aggregator/podmonitor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package aggregator

import (
"testing"

. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

vectorv1alpha1 "github.com/kaasops/vector-operator/api/v1alpha1"
)

// Helper function to create a Controller for testing
func createTestController(name, namespace string, spec *vectorv1alpha1.VectorAggregatorCommon, isCluster bool) *Controller {
agg := &vectorv1alpha1.VectorAggregator{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
}

return &Controller{
Name: name,
Namespace: namespace,
VectorAggregator: agg,
APIVersion: "observability.kaasops.io/v1alpha1",
Kind: "VectorAggregator",
Spec: spec,
isClusterAggregator: isCluster,
}
}

func TestCreateVectorAggregatorPodMonitor_WithCustomSettings(t *testing.T) {
g := NewWithT(t)

ctrl := createTestController("test-aggregator", "default",
&vectorv1alpha1.VectorAggregatorCommon{
VectorCommon: vectorv1alpha1.VectorCommon{
ScrapeInterval: "60s",
ScrapeTimeout: "20s",
},
}, false)

pm := ctrl.createVectorAggregatorPodMonitor()

// Verify PodMonitor structure
g.Expect(pm).NotTo(BeNil(), "PodMonitor should not be nil")
g.Expect(pm.Spec.PodMetricsEndpoints).To(HaveLen(1), "Should have exactly one endpoint")

// Verify scrape settings
endpoint := pm.Spec.PodMetricsEndpoints[0]
g.Expect(string(endpoint.Interval)).To(Equal("60s"), "scrapeInterval should be 60s")
g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("20s"), "scrapeTimeout should be 20s")

// Verify endpoint configuration
g.Expect(endpoint.Port).To(Equal("prom-exporter"), "Port should be prom-exporter")
g.Expect(endpoint.Path).To(Equal("/metrics"), "Path should be /metrics")

// Verify metadata
g.Expect(pm.ObjectMeta.Namespace).To(Equal("default"), "Namespace should match Aggregator namespace")
}

func TestCreateVectorAggregatorPodMonitor_WithDefaults(t *testing.T) {
g := NewWithT(t)

ctrl := createTestController("test-aggregator", "default",
&vectorv1alpha1.VectorAggregatorCommon{
VectorCommon: vectorv1alpha1.VectorCommon{
// No scrape settings specified
},
}, false)

pm := ctrl.createVectorAggregatorPodMonitor()

g.Expect(pm).NotTo(BeNil())
g.Expect(pm.Spec.PodMetricsEndpoints).To(HaveLen(1))

endpoint := pm.Spec.PodMetricsEndpoints[0]
// When not specified, fields should be empty (Prometheus will use defaults)
g.Expect(string(endpoint.Interval)).To(BeEmpty(), "Interval should be empty when not specified")
g.Expect(string(endpoint.ScrapeTimeout)).To(BeEmpty(), "ScrapeTimeout should be empty when not specified")

// Basic endpoint config should still be set
g.Expect(endpoint.Port).To(Equal("prom-exporter"))
g.Expect(endpoint.Path).To(Equal("/metrics"))
}

func TestCreateVectorAggregatorPodMonitor_LabelSelector(t *testing.T) {
g := NewWithT(t)

ctrl := createTestController("test-aggregator", "test-namespace",
&vectorv1alpha1.VectorAggregatorCommon{}, false)

pm := ctrl.createVectorAggregatorPodMonitor()

// Verify selector has proper labels to target only Aggregator pods
g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/component", "Aggregator"),
"Selector should include component=Aggregator label")
g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/instance", "test-aggregator"),
"Selector should include instance label matching Aggregator name")
}

func TestCreateVectorAggregatorPodMonitor_OnlyIntervalSet(t *testing.T) {
g := NewWithT(t)

ctrl := createTestController("test-aggregator", "default",
&vectorv1alpha1.VectorAggregatorCommon{
VectorCommon: vectorv1alpha1.VectorCommon{
ScrapeInterval: "1m",
// ScrapeTimeout not set
},
}, false)

pm := ctrl.createVectorAggregatorPodMonitor()

endpoint := pm.Spec.PodMetricsEndpoints[0]
g.Expect(string(endpoint.Interval)).To(Equal("1m"), "Interval should be set")
g.Expect(string(endpoint.ScrapeTimeout)).To(BeEmpty(), "Timeout should remain empty")
}

func TestCreateVectorAggregatorPodMonitor_OnlyTimeoutSet(t *testing.T) {
g := NewWithT(t)

ctrl := createTestController("test-aggregator", "default",
&vectorv1alpha1.VectorAggregatorCommon{
VectorCommon: vectorv1alpha1.VectorCommon{
// ScrapeInterval not set
ScrapeTimeout: "30s",
},
}, false)

pm := ctrl.createVectorAggregatorPodMonitor()

endpoint := pm.Spec.PodMetricsEndpoints[0]
g.Expect(string(endpoint.Interval)).To(BeEmpty(), "Interval should remain empty")
g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("30s"), "Timeout should be set")
}

func TestCreateVectorAggregatorPodMonitor_DurationFormats(t *testing.T) {
testCases := []struct {
name string
interval string
timeout string
expectedInt string
expectedTime string
}{
{
name: "Seconds format",
interval: "60s",
timeout: "20s",
expectedInt: "60s",
expectedTime: "20s",
},
{
name: "Minutes format",
interval: "10m",
timeout: "2m",
expectedInt: "10m",
expectedTime: "2m",
},
{
name: "Mixed format",
interval: "2m30s",
timeout: "45s",
expectedInt: "2m30s",
expectedTime: "45s",
},
{
name: "Milliseconds format",
interval: "1000ms",
timeout: "500ms",
expectedInt: "1000ms",
expectedTime: "500ms",
},
{
name: "Hours format",
interval: "2h",
timeout: "1h",
expectedInt: "2h",
expectedTime: "1h",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
g := NewWithT(t)

ctrl := createTestController("test-aggregator", "default",
&vectorv1alpha1.VectorAggregatorCommon{
VectorCommon: vectorv1alpha1.VectorCommon{
ScrapeInterval: tc.interval,
ScrapeTimeout: tc.timeout,
},
}, false)

pm := ctrl.createVectorAggregatorPodMonitor()
endpoint := pm.Spec.PodMetricsEndpoints[0]

g.Expect(string(endpoint.Interval)).To(Equal(tc.expectedInt))
g.Expect(string(endpoint.ScrapeTimeout)).To(Equal(tc.expectedTime))
})
}
}

func TestCreateVectorAggregatorPodMonitor_ClusterAggregator(t *testing.T) {
g := NewWithT(t)

ctrl := createTestController("cluster-test-aggregator", "vector-system",
&vectorv1alpha1.VectorAggregatorCommon{
VectorCommon: vectorv1alpha1.VectorCommon{
ScrapeInterval: "90s",
ScrapeTimeout: "25s",
},
}, true)

pm := ctrl.createVectorAggregatorPodMonitor()

// Verify ClusterVectorAggregator also gets proper PodMonitor
g.Expect(pm).NotTo(BeNil())

endpoint := pm.Spec.PodMetricsEndpoints[0]
g.Expect(string(endpoint.Interval)).To(Equal("90s"))
g.Expect(string(endpoint.ScrapeTimeout)).To(Equal("25s"))

// Verify selector for ClusterVectorAggregator
g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/component", "Aggregator"))
g.Expect(pm.Spec.Selector.MatchLabels).To(HaveKeyWithValue("app.kubernetes.io/instance", "cluster-test-aggregator"))
}
19 changes: 13 additions & 6 deletions internal/vector/vectoragent/vectoragent_podmonitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,22 @@ func (ctrl *Controller) createVectorAgentPodMonitor() *monitorv1.PodMonitor {
matchLabels := ctrl.matchLabelsForVectorAgent()
annotations := ctrl.annotationsForVectorAgent()

endpoint := monitorv1.PodMetricsEndpoint{
Path: "/metrics",
Port: "prom-exporter",
}

if ctrl.Vector.Spec.Agent.ScrapeInterval != "" {
endpoint.Interval = monitorv1.Duration(ctrl.Vector.Spec.Agent.ScrapeInterval)
}
if ctrl.Vector.Spec.Agent.ScrapeTimeout != "" {
endpoint.ScrapeTimeout = monitorv1.Duration(ctrl.Vector.Spec.Agent.ScrapeTimeout)
}

podmonitor := &monitorv1.PodMonitor{
ObjectMeta: ctrl.objectMetaVectorAgent(labels, annotations, ctrl.Vector.Namespace),
Spec: monitorv1.PodMonitorSpec{
PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{
{
Path: "/metrics",
Port: "prom-exporter",
},
},
PodMetricsEndpoints: []monitorv1.PodMetricsEndpoint{endpoint},
Selector: metav1.LabelSelector{
MatchLabels: matchLabels,
},
Expand Down
Loading