Skip to content
Open
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,21 @@ Once the `ParseableConfig` CR is applied, PAI automatically creates the followin
### Metrics
- **Pod metrics**: Container CPU, memory, network via `kubeletstats` and `k8s_cluster` receivers
- **Node metrics**: Node-level CPU, memory, disk, network via `kubeletstats` receiver
- **Traefik metrics**: Optional built-in Prometheus scrape of Traefik pods on `:9100/metrics`
- Namespace filtering via `namespaceSelector`

Enable Traefik metrics explicitly:

```yaml
spec:
metrics:
traefik:
enabled: true
targetDataset: traefik-metrics
```

PAI selects pods labeled `app.kubernetes.io/name=traefik`. The scrape is disabled by default. Use `port`, `uri`, or `namespaceSelector` under `traefik` when your deployment differs from the defaults.

### Events
- Kubernetes events collected via `k8sobjects` receiver in watch mode
- Namespace filtering via `namespaceSelector`
Expand Down
29 changes: 28 additions & 1 deletion api/v1alpha1/parseableconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,39 @@ type ScrapeConfig struct {
PodSelector map[string]string `json:"podSelector,omitempty"`
}

// TraefikMetricsConfig configures the built-in Traefik Prometheus scrape.
// PAI discovers pods with app.kubernetes.io/name=traefik and scrapes
// /metrics on port 9100.
type TraefikMetricsConfig struct {
// Enabled controls whether Traefik metrics are collected.
Enabled bool `json:"enabled"`

// TargetDataset is the Parseable dataset for Traefik metrics.
TargetDataset string `json:"targetDataset,omitempty"`

// Headers are additional HTTP headers for the exporter. Overrides global headers with the same key.
Headers map[string]string `json:"headers,omitempty"`

// NamespaceSelector limits Traefik pod discovery to matching namespaces.
NamespaceSelector NamespaceSelector `json:"namespaceSelector,omitempty"`

// Port is the Traefik metrics port. Defaults to 9100 when omitted.
Port int32 `json:"port,omitempty"`

// URI is the Traefik metrics path. Defaults to /metrics when omitted.
URI string `json:"uri,omitempty"`
}

// MetricsConfig defines metrics configuration. ClusterMetrics enables built-in
// kubelet/cluster metrics; ScrapeConfigs adds Prometheus-style scrape pipelines.
// kubelet/cluster metrics; Traefik enables built-in Traefik discovery;
// ScrapeConfigs adds custom Prometheus-style scrape pipelines.
type MetricsConfig struct {
// ClusterMetrics toggles built-in node/pod/cluster metrics via kubeletstats + k8s_cluster receivers
ClusterMetrics *ClusterMetricsConfig `json:"clusterMetrics,omitempty"`

// Traefik enables the built-in Traefik Prometheus scrape.
Traefik *TraefikMetricsConfig `json:"traefik,omitempty"`

// ScrapeConfigs is a list of Prometheus-style scrape pipelines
ScrapeConfigs []ScrapeConfig `json:"scrapeConfigs,omitempty"`
}
Expand Down
28 changes: 28 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 46 additions & 0 deletions config/crd/bases/observability.parseable.com_parseableconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,52 @@ spec:
- uri
type: object
type: array
traefik:
description: Traefik enables the built-in Traefik Prometheus scrape.
properties:
enabled:
description: Enabled controls whether Traefik metrics are
collected.
type: boolean
headers:
additionalProperties:
type: string
description: Headers are additional HTTP headers for the exporter.
Overrides global headers with the same key.
type: object
namespaceSelector:
description: NamespaceSelector limits Traefik pod discovery
to matching namespaces.
properties:
mode:
description: Mode specifies whether to include or exclude
the listed namespaces
enum:
- include
- exclude
type: string
namespaces:
description: Namespaces is the list of namespace names
items:
type: string
type: array
type: object
port:
description: Port is the Traefik metrics port. Defaults to
9100 when omitted.
format: int32
type: integer
targetDataset:
description: TargetDataset is the Parseable dataset for Traefik
metrics.
type: string
uri:
description: URI is the Traefik metrics path. Defaults to
/metrics when omitted.
type: string
required:
- enabled
type: object
type: object
paused:
description: |-
Expand Down
34 changes: 32 additions & 2 deletions internal/controller/parseableconfig_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ func (r *ParseableConfigReconciler) ensureMetricsEventsCollector(ctx context.Con
if anyClusterMetricEnabled(config.Spec.Metrics.ClusterMetrics) {
metricsEnabled = true
}
for _, sc := range config.Spec.Metrics.ScrapeConfigs {
for _, sc := range effectiveScrapeConfigs(config.Spec.Metrics) {
if sc.Name != "" && sc.TargetDataset != "" && sc.Port > 0 {
metricsEnabled = true
break
Expand Down Expand Up @@ -1045,7 +1045,7 @@ func (r *ParseableConfigReconciler) buildMetricsEventsCollectorConfig(

// Per-scrape-entry Prometheus pipelines with Kubernetes pod service discovery.
if config.Spec.Metrics != nil {
for _, sc := range config.Spec.Metrics.ScrapeConfigs {
for _, sc := range effectiveScrapeConfigs(config.Spec.Metrics) {
id := sanitizeName(sc.Name)
if id == "" || sc.TargetDataset == "" || sc.Port <= 0 {
continue
Expand Down Expand Up @@ -1223,6 +1223,36 @@ func sanitizePromLabel(s string) string {
return b.String()
}

// effectiveScrapeConfigs expands opt-in built-in integrations into the same
// generic scrape representation used by custom scrapeConfigs.
func effectiveScrapeConfigs(metrics *observabilityv1alpha1.MetricsConfig) []observabilityv1alpha1.ScrapeConfig {
if metrics == nil {
return nil
}

configs := make([]observabilityv1alpha1.ScrapeConfig, 0, len(metrics.ScrapeConfigs)+1)
if traefik := metrics.Traefik; traefik != nil && traefik.Enabled && traefik.TargetDataset != "" {
port := traefik.Port
if port <= 0 {
port = 9100
}
uri := traefik.URI
if uri == "" {
uri = "/metrics"
}
configs = append(configs, observabilityv1alpha1.ScrapeConfig{
Name: "traefik",
URI: uri,
Port: port,
TargetDataset: traefik.TargetDataset,
Headers: traefik.Headers,
NamespaceSelector: traefik.NamespaceSelector,
PodSelector: map[string]string{"app.kubernetes.io/name": "traefik"},
})
}
return append(configs, metrics.ScrapeConfigs...)
}

// anyClusterMetricEnabled reports whether at least one built-in cluster-metrics
// receiver is enabled with a target dataset to ship to.
func anyClusterMetricEnabled(cm *observabilityv1alpha1.ClusterMetricsConfig) bool {
Expand Down
68 changes: 68 additions & 0 deletions internal/controller/traefik_metrics_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package controller

import (
"testing"

observabilityv1alpha1 "github.com/parseable/pai/api/v1alpha1"
)

func TestEffectiveScrapeConfigsTraefikDisabledByDefault(t *testing.T) {
configs := effectiveScrapeConfigs(&observabilityv1alpha1.MetricsConfig{})
if len(configs) != 0 {
t.Fatalf("expected no scrape configs, got %d", len(configs))
}
}

func TestEffectiveScrapeConfigsTraefikDefaults(t *testing.T) {
configs := effectiveScrapeConfigs(&observabilityv1alpha1.MetricsConfig{
Traefik: &observabilityv1alpha1.TraefikMetricsConfig{
Enabled: true,
TargetDataset: "traefik-metrics",
},
})

if len(configs) != 1 {
t.Fatalf("expected one scrape config, got %d", len(configs))
}
got := configs[0]
if got.Name != "traefik" || got.Port != 9100 || got.URI != "/metrics" {
t.Fatalf("unexpected Traefik defaults: %#v", got)
}
if got.TargetDataset != "traefik-metrics" {
t.Fatalf("unexpected dataset: %q", got.TargetDataset)
}
if got.PodSelector["app.kubernetes.io/name"] != "traefik" {
t.Fatalf("unexpected pod selector: %#v", got.PodSelector)
}
}

func TestEffectiveScrapeConfigsTraefikOverrides(t *testing.T) {
metrics := &observabilityv1alpha1.MetricsConfig{
Traefik: &observabilityv1alpha1.TraefikMetricsConfig{
Enabled: true,
TargetDataset: "edge-metrics",
Port: 9200,
URI: "custom-metrics",
Headers: map[string]string{"X-P-Team": "platform"},
NamespaceSelector: observabilityv1alpha1.NamespaceSelector{
Mode: "include",
Namespaces: []string{"edge"},
},
},
ScrapeConfigs: []observabilityv1alpha1.ScrapeConfig{{
Name: "custom", Port: 8080, URI: "/metrics", TargetDataset: "custom-metrics",
}},
}

configs := effectiveScrapeConfigs(metrics)
if len(configs) != 2 {
t.Fatalf("expected built-in and custom scrape configs, got %d", len(configs))
}
got := configs[0]
if got.Port != 9200 || got.URI != "custom-metrics" || got.TargetDataset != "edge-metrics" {
t.Fatalf("overrides not preserved: %#v", got)
}
if got.Headers["X-P-Team"] != "platform" || len(got.NamespaceSelector.Namespaces) != 1 {
t.Fatalf("headers or namespace selector not preserved: %#v", got)
}
}