Skip to content

Commit a4c4905

Browse files
committed
add `inject-per-series-metadata'
See the discussion here #2551 This frees users from having to join on the various label series, which is useful when infrastructure has agents that can't do recording rules.
1 parent 5f11691 commit a4c4905

File tree

6 files changed

+49
-13
lines changed

6 files changed

+49
-13
lines changed

internal/store/builder.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ type Builder struct {
7575
buildCustomResourceStoresFunc ksmtypes.BuildCustomResourceStoresFunc
7676
allowAnnotationsList map[string][]string
7777
allowLabelsList map[string][]string
78+
injectPerSeriesMetadata bool
7879
utilOptions *options.Options
7980
// namespaceFilter is inside fieldSelectorFilter
8081
fieldSelectorFilter string
@@ -245,6 +246,13 @@ func (b *Builder) allowList(list map[string][]string) (map[string][]string, erro
245246
return m, nil
246247
}
247248

249+
// WithAllowAnnotations configures which annotations can be returned for metrics
250+
func (b *Builder) WithInjectPerSeriesMetadata(inject bool) error {
251+
var err error
252+
b.injectPerSeriesMetadata = inject
253+
return err
254+
}
255+
248256
// WithAllowAnnotations configures which annotations can be returned for metrics
249257
func (b *Builder) WithAllowAnnotations(annotations map[string][]string) error {
250258
var err error
@@ -463,7 +471,7 @@ func (b *Builder) buildStorageClassStores() []cache.Store {
463471
}
464472

465473
func (b *Builder) buildPodStores() []cache.Store {
466-
return b.buildStoresFunc(podMetricFamilies(b.allowAnnotationsList["pods"], b.allowLabelsList["pods"]), &v1.Pod{}, createPodListWatch, b.useAPIServerCache)
474+
return b.buildStoresFunc(podMetricFamilies(b.injectPerSeriesMetadata, b.allowAnnotationsList["pods"], b.allowLabelsList["pods"]), &v1.Pod{}, createPodListWatch, b.useAPIServerCache)
467475
}
468476

469477
func (b *Builder) buildCsrStores() []cache.Store {

internal/store/pod.go

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ var (
4040
podStatusReasons = []string{"Evicted", "NodeAffinity", "NodeLost", "Shutdown", "UnexpectedAdmissionError"}
4141
)
4242

43-
func podMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator {
43+
func podMetricFamilies(injectPerSeriesMetadata bool, allowAnnotationsList []string, allowLabelsList []string) []generator.FamilyGenerator {
44+
mc := &MetricConfig{
45+
InjectPerSeriesMetadata: injectPerSeriesMetadata,
46+
AllowAnnotations: allowAnnotationsList,
47+
AllowLabels: allowLabelsList,
48+
}
4449
return []generator.FamilyGenerator{
4550
createPodCompletionTimeFamilyGenerator(),
4651
createPodContainerInfoFamilyGenerator(),
@@ -82,7 +87,7 @@ func podMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generat
8287
createPodSpecVolumesPersistentVolumeClaimsInfoFamilyGenerator(),
8388
createPodSpecVolumesPersistentVolumeClaimsReadonlyFamilyGenerator(),
8489
createPodStartTimeFamilyGenerator(),
85-
createPodStatusPhaseFamilyGenerator(),
90+
createPodStatusPhaseFamilyGenerator(mc),
8691
createPodStatusQosClassFamilyGenerator(),
8792
createPodStatusReadyFamilyGenerator(),
8893
createPodStatusReadyTimeFamilyGenerator(),
@@ -1333,7 +1338,7 @@ func createPodStartTimeFamilyGenerator() generator.FamilyGenerator {
13331338
)
13341339
}
13351340

1336-
func createPodStatusPhaseFamilyGenerator() generator.FamilyGenerator {
1341+
func createPodStatusPhaseFamilyGenerator(mc *MetricConfig) generator.FamilyGenerator {
13371342
return *generator.NewFamilyGeneratorWithStability(
13381343
"kube_pod_status_phase",
13391344
"The pods current phase.",
@@ -1361,13 +1366,12 @@ func createPodStatusPhaseFamilyGenerator() generator.FamilyGenerator {
13611366

13621367
ms := make([]*metric.Metric, len(phases))
13631368

1364-
for i, p := range phases {
1365-
ms[i] = &metric.Metric{
1366-
1369+
for i, ph := range phases {
1370+
ms[i] = injectLabelsAndAnnos(&metric.Metric{
13671371
LabelKeys: []string{"phase"},
1368-
LabelValues: []string{p.n},
1369-
Value: boolFloat64(p.v),
1370-
}
1372+
LabelValues: []string{ph.n},
1373+
Value: boolFloat64(ph.v),
1374+
}, mc, &p.ObjectMeta)
13711375
}
13721376

13731377
return &metric.Family{

internal/store/pod_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,8 +2181,8 @@ func TestPodStore(t *testing.T) {
21812181
}
21822182

21832183
for i, c := range cases {
2184-
c.Func = generator.ComposeMetricGenFuncs(podMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList))
2185-
c.Headers = generator.ExtractMetricFamilyHeaders(podMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList))
2184+
c.Func = generator.ComposeMetricGenFuncs(podMetricFamilies(false, c.AllowAnnotationsList, c.AllowLabelsList))
2185+
c.Headers = generator.ExtractMetricFamilyHeaders(podMetricFamilies(false, c.AllowAnnotationsList, c.AllowLabelsList))
21862186
if err := c.run(); err != nil {
21872187
t.Errorf("unexpected collecting result in %vth run:\n%s", i, err)
21882188
}
@@ -2192,7 +2192,7 @@ func TestPodStore(t *testing.T) {
21922192
func BenchmarkPodStore(b *testing.B) {
21932193
b.ReportAllocs()
21942194

2195-
f := generator.ComposeMetricGenFuncs(podMetricFamilies(nil, nil))
2195+
f := generator.ComposeMetricGenFuncs(podMetricFamilies(false, nil, nil))
21962196

21972197
pod := &v1.Pod{
21982198
ObjectMeta: metav1.ObjectMeta{

internal/store/utils.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"k8s.io/apimachinery/pkg/api/resource"
2727

2828
v1 "k8s.io/api/core/v1"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930
"k8s.io/apimachinery/pkg/util/validation"
3031

3132
"k8s.io/kube-state-metrics/v2/pkg/metric"
@@ -38,6 +39,12 @@ var (
3839
conditionStatuses = []v1.ConditionStatus{v1.ConditionTrue, v1.ConditionFalse, v1.ConditionUnknown}
3940
)
4041

42+
type MetricConfig struct {
43+
InjectPerSeriesMetadata bool
44+
AllowAnnotations []string
45+
AllowLabels []string
46+
}
47+
4148
func resourceVersionMetric(rv string) []*metric.Metric {
4249
v, err := strconv.ParseFloat(rv, 64)
4350
if err != nil {
@@ -175,6 +182,17 @@ func isPrefixedNativeResource(name v1.ResourceName) bool {
175182
return strings.Contains(string(name), v1.ResourceDefaultNamespacePrefix)
176183
}
177184

185+
// convenience wrapper to inject allow-listed labels and annotations to a metric if per-series injection is enabled.
186+
func injectLabelsAndAnnos(m *metric.Metric, metricConfig *MetricConfig, obj *metav1.ObjectMeta) *metric.Metric {
187+
if !metricConfig.InjectPerSeriesMetadata {
188+
return m
189+
}
190+
labelKeys, labelValues := createPrometheusLabelKeysValues("label", obj.Labels, metricConfig.AllowLabels)
191+
annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", obj.Annotations, metricConfig.AllowAnnotations)
192+
m.LabelKeys, m.LabelValues = mergeKeyValues(m.LabelKeys, m.LabelValues, annotationKeys, annotationValues, labelKeys, labelValues)
193+
return m
194+
}
195+
178196
// createPrometheusLabelKeysValues takes in passed kubernetes annotations/labels
179197
// and associated allowed list in kubernetes label format.
180198
// It returns only those allowed annotations/labels that exist in the list and converts them to Prometheus labels.

pkg/app/server.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,9 @@ func RunKubeStateMetrics(ctx context.Context, opts *options.Options) error {
264264
if err := storeBuilder.WithAllowLabels(opts.LabelsAllowList); err != nil {
265265
return fmt.Errorf("failed to set up labels allowlist: %v", err)
266266
}
267+
if err := storeBuilder.WithInjectPerSeriesMetadata(opts.InjectPerSeriesMetadata); err != nil {
268+
return fmt.Errorf("failed to configure per series metadata injection: %v", err)
269+
}
267270

268271
ksmMetricsRegistry.MustRegister(
269272
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),

pkg/options/options.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ type Options struct {
4747
MetricOptInList MetricSet `yaml:"metric_opt_in_list"`
4848
Resources ResourceSet `yaml:"resources"`
4949

50+
InjectPerSeriesMetadata bool `yaml:"inject_per_series_metadata"`
51+
5052
cmd *cobra.Command
5153
Apiserver string `yaml:"apiserver"`
5254
CustomResourceConfig string `yaml:"custom_resource_config"`
@@ -164,6 +166,7 @@ func (o *Options) AddFlags(cmd *cobra.Command) {
164166
o.cmd.Flags().Var(&o.LabelsAllowList, "metric-labels-allowlist", "Comma-separated list of additional Kubernetes label keys that will be used in the resource' labels metric. By default the labels metrics are not exposed. To include them, provide a list of resource names in their plural form and Kubernetes label keys you would like to allow for them (Example: '=namespaces=[k8s-label-1,k8s-label-n,...],pods=[app],...)'. A single '*' can be provided per resource instead to allow any labels, but that has severe performance implications (Example: '=pods=[*]'). Additionally, an asterisk (*) can be provided as a key, which will resolve to all resources, i.e., assuming '--resources=deployments,pods', '=*=[*]' will resolve to '=deployments=[*],pods=[*]'.")
165167
o.cmd.Flags().Var(&o.MetricAllowlist, "metric-allowlist", "Comma-separated list of metrics to be exposed. This list comprises of exact metric names and/or *ECMAScript-based* regex patterns. The allowlist and denylist are mutually exclusive.")
166168
o.cmd.Flags().Var(&o.MetricDenylist, "metric-denylist", "Comma-separated list of metrics not to be enabled. This list comprises of exact metric names and/or *ECMAScript-based* regex patterns. The allowlist and denylist are mutually exclusive.")
169+
o.cmd.Flags().BoolVar(&o.InjectPerSeriesMetadata, "inject-per-series-metadata", false, "Propagate labels and annotations from object metadata to all respective metrics rather than simply populating kube_<resource>_labels and kube_<resource>_annotations. Honors metric-annotations-allowlist and metric-labels-allowlist.")
167170
o.cmd.Flags().Var(&o.MetricOptInList, "metric-opt-in-list", "Comma-separated list of metrics which are opt-in and not enabled by default. This is in addition to the metric allow- and denylists")
168171
o.cmd.Flags().Var(&o.Namespaces, "namespaces", fmt.Sprintf("Comma-separated list of namespaces to be enabled. Defaults to %q", &DefaultNamespaces))
169172
o.cmd.Flags().Var(&o.NamespacesDenylist, "namespaces-denylist", "Comma-separated list of namespaces not to be enabled. If namespaces and namespaces-denylist are both set, only namespaces that are excluded in namespaces-denylist will be used.")

0 commit comments

Comments
 (0)