Skip to content

Commit 42b2197

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 b6499c9 commit 42b2197

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
@@ -242,6 +243,13 @@ func (b *Builder) allowList(list map[string][]string) (map[string][]string, erro
242243
return m, nil
243244
}
244245

246+
// WithAllowAnnotations configures which annotations can be returned for metrics
247+
func (b *Builder) WithInjectPerSeriesMetadata(inject bool) error {
248+
var err error
249+
b.injectPerSeriesMetadata = inject
250+
return err
251+
}
252+
245253
// WithAllowAnnotations configures which annotations can be returned for metrics
246254
func (b *Builder) WithAllowAnnotations(annotations map[string][]string) error {
247255
var err error
@@ -460,7 +468,7 @@ func (b *Builder) buildStorageClassStores() []cache.Store {
460468
}
461469

462470
func (b *Builder) buildPodStores() []cache.Store {
463-
return b.buildStoresFunc(podMetricFamilies(b.allowAnnotationsList["pods"], b.allowLabelsList["pods"]), &v1.Pod{}, createPodListWatch, b.useAPIServerCache)
471+
return b.buildStoresFunc(podMetricFamilies(b.injectPerSeriesMetadata, b.allowAnnotationsList["pods"], b.allowLabelsList["pods"]), &v1.Pod{}, createPodListWatch, b.useAPIServerCache)
464472
}
465473

466474
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
@@ -2175,8 +2175,8 @@ func TestPodStore(t *testing.T) {
21752175
}
21762176

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

2189-
f := generator.ComposeMetricGenFuncs(podMetricFamilies(nil, nil))
2189+
f := generator.ComposeMetricGenFuncs(podMetricFamilies(false, nil, nil))
21902190

21912191
pod := &v1.Pod{
21922192
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 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 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 to all respective metrics rather than just kube_<resource>_labels and kube_<resource>_annotations.")
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)