diff --git a/docs/README.md b/docs/README.md index 617c28f39e..6e5e48e074 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,6 +65,7 @@ See each file for specific documentation about the exposed metrics: - [ClusterRole Metrics](clusterrole-metrics.md) - [ClusterRoleBinding Metrics](clusterrolebinding-metrics.md) +- [IngressClass Metrics](ingressclass-metrics.md) - [Role Metrics](role-metrics.md) - [RoleBinding Metrics](rolebinding-metrics.md) - [ServiceAccount Metrics](serviceaccount-metrics.md) diff --git a/docs/ingressclass-metrics.md b/docs/ingressclass-metrics.md new file mode 100644 index 0000000000..1be511d350 --- /dev/null +++ b/docs/ingressclass-metrics.md @@ -0,0 +1,8 @@ +# IngressClass Metrics + +| Metric name| Metric type | Labels/tags | Status | +| ---------- | ----------- | ----------- | ----------- | +| kube_ingressclass_annotations | Gauge | `ingressclass`=<ingressclass-name>
`annotation_INGRESSCLASS_ANNOTATION`=<INGRESSCLASS_ANNOTATION> | EXPERIMENTAL | +| kube_ingressclass_info | Gauge | `ingressclass`=<ingressclass-name>
`controller`=<ingress-controller-name>
| STABLE | +| kube_ingressclass_labels | Gauge | `ingressclass`=<ingressclass-name>
`label_INGRESSCLASS_LABEL`=<INGRESSCLASS_LABEL> | STABLE | +| kube_ingressclass_created | Gauge | `ingressclass`=<ingressclass-name> | STABLE | diff --git a/examples/autosharding/cluster-role.yaml b/examples/autosharding/cluster-role.yaml index 2a54e60129..235ed7f570 100644 --- a/examples/autosharding/cluster-role.yaml +++ b/examples/autosharding/cluster-role.yaml @@ -97,6 +97,7 @@ rules: - networking.k8s.io resources: - networkpolicies + - ingressclasses - ingresses verbs: - list diff --git a/examples/standard/cluster-role.yaml b/examples/standard/cluster-role.yaml index 2a54e60129..235ed7f570 100644 --- a/examples/standard/cluster-role.yaml +++ b/examples/standard/cluster-role.yaml @@ -97,6 +97,7 @@ rules: - networking.k8s.io resources: - networkpolicies + - ingressclasses - ingresses verbs: - list diff --git a/internal/store/builder.go b/internal/store/builder.go index 11dbca14ba..35786cdaf5 100644 --- a/internal/store/builder.go +++ b/internal/store/builder.go @@ -295,6 +295,7 @@ var availableStores = map[string]func(f *Builder) []cache.Store{ "endpoints": func(b *Builder) []cache.Store { return b.buildEndpointsStores() }, "horizontalpodautoscalers": func(b *Builder) []cache.Store { return b.buildHPAStores() }, "ingresses": func(b *Builder) []cache.Store { return b.buildIngressStores() }, + "ingressclasses": func(b *Builder) []cache.Store { return b.buildIngressClassStores() }, "jobs": func(b *Builder) []cache.Store { return b.buildJobStores() }, "leases": func(b *Builder) []cache.Store { return b.buildLeasesStores() }, "limitranges": func(b *Builder) []cache.Store { return b.buildLimitRangeStores() }, @@ -470,6 +471,10 @@ func (b *Builder) buildRoleBindingStores() []cache.Store { return b.buildStoresFunc(roleBindingMetricFamilies(b.allowAnnotationsList["rolebindings"], b.allowLabelsList["rolebindings"]), &rbacv1.RoleBinding{}, createRoleBindingListWatch, b.useAPIServerCache) } +func (b *Builder) buildIngressClassStores() []cache.Store { + return b.buildStoresFunc(ingressClassMetricFamilies(b.allowAnnotationsList["ingressclass"], b.allowLabelsList["ingressclass"]), &networkingv1.IngressClass{}, createIngressClassListWatch, b.useAPIServerCache) +} + func (b *Builder) buildStores( metricFamilies []generator.FamilyGenerator, expectedType interface{}, diff --git a/internal/store/ingressclass.go b/internal/store/ingressclass.go new file mode 100644 index 0000000000..f86a3180d3 --- /dev/null +++ b/internal/store/ingressclass.go @@ -0,0 +1,134 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package store + +import ( + "context" + + "k8s.io/kube-state-metrics/v2/pkg/metric" + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" +) + +var ( + descIngressClassAnnotationsName = "kube_ingressclass_annotations" + descIngressClassAnnotationsHelp = "Kubernetes annotations converted to Prometheus labels." + descIngressClassLabelsName = "kube_ingressclass_labels" + descIngressClassLabelsHelp = "Kubernetes labels converted to Prometheus labels." + descIngressClassLabelsDefaultLabels = []string{"ingressclass"} +) + +func ingressClassMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator { + return []generator.FamilyGenerator{ + *generator.NewFamilyGenerator( + "kube_ingressclass_info", + "Information about ingressclass.", + metric.Gauge, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + + m := metric.Metric{ + LabelKeys: []string{"controller"}, + LabelValues: []string{s.Spec.Controller}, + Value: 1, + } + return &metric.Family{Metrics: []*metric.Metric{&m}} + }), + ), + *generator.NewFamilyGenerator( + "kube_ingressclass_created", + "Unix creation timestamp", + metric.Gauge, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + ms := []*metric.Metric{} + if !s.CreationTimestamp.IsZero() { + ms = append(ms, &metric.Metric{ + Value: float64(s.CreationTimestamp.Unix()), + }) + } + return &metric.Family{ + Metrics: ms, + } + }), + ), + *generator.NewFamilyGenerator( + descIngressClassAnnotationsName, + descIngressClassAnnotationsHelp, + metric.Gauge, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", s.Annotations, allowAnnotationsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: annotationKeys, + LabelValues: annotationValues, + Value: 1, + }, + }, + } + }), + ), + *generator.NewFamilyGenerator( + descIngressClassLabelsName, + descIngressClassLabelsHelp, + metric.Gauge, + "", + wrapIngressClassFunc(func(s *networkingv1.IngressClass) *metric.Family { + labelKeys, labelValues := createPrometheusLabelKeysValues("label", s.Labels, allowLabelsList) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }, + }, + } + }), + ), + } +} + +func wrapIngressClassFunc(f func(*networkingv1.IngressClass) *metric.Family) func(interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + ingressClass := obj.(*networkingv1.IngressClass) + + metricFamily := f(ingressClass) + + for _, m := range metricFamily.Metrics { + m.LabelKeys, m.LabelValues = mergeKeyValues(descIngressClassLabelsDefaultLabels, []string{ingressClass.Name}, m.LabelKeys, m.LabelValues) + } + + return metricFamily + } +} + +func createIngressClassListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher { + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + return kubeClient.NetworkingV1().IngressClasses().List(context.TODO(), opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + return kubeClient.NetworkingV1().IngressClasses().Watch(context.TODO(), opts) + }, + } +} diff --git a/internal/store/ingressclass_test.go b/internal/store/ingressclass_test.go new file mode 100644 index 0000000000..50312d0ca3 --- /dev/null +++ b/internal/store/ingressclass_test.go @@ -0,0 +1,105 @@ +/* +Copyright 2022 The Kubernetes Authors All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package store + +import ( + "testing" + + networkingv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator" +) + +func TestIngressClassStore(t *testing.T) { + startTime := 1501569018 + metav1StartTime := metav1.Unix(int64(startTime), 0) + + cases := []generateMetricsTestCase{ + { + Obj: &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_ingressclass-info", + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "controller", + }, + }, + Want: ` + # HELP kube_ingressclass_info Information about ingressclass. + # TYPE kube_ingressclass_info gauge + kube_ingressclass_info{ingressclass="test_ingressclass-info",controller="controller"} 1 + `, + MetricNames: []string{ + "kube_ingressclass_info", + }, + }, + { + Obj: &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_kube_ingressclass-created", + CreationTimestamp: metav1StartTime, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "controller", + }, + }, + Want: ` + # HELP kube_ingressclass_created Unix creation timestamp + # TYPE kube_ingressclass_created gauge + kube_ingressclass_created{ingressclass="test_kube_ingressclass-created"} 1.501569018e+09 + `, + MetricNames: []string{ + "kube_ingressclass_created", + }, + }, + { + AllowAnnotationsList: []string{ + "ingressclass.kubernetes.io/is-default-class", + }, + Obj: &networkingv1.IngressClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_ingressclass-labels", + Annotations: map[string]string{ + "ingressclass.kubernetes.io/is-default-class": "true", + }, + Labels: map[string]string{ + "foo": "bar", + }, + }, + Spec: networkingv1.IngressClassSpec{ + Controller: "controller", + }, + }, + Want: ` + # HELP kube_ingressclass_annotations Kubernetes annotations converted to Prometheus labels. + # HELP kube_ingressclass_labels Kubernetes labels converted to Prometheus labels. + # TYPE kube_ingressclass_annotations gauge + # TYPE kube_ingressclass_labels gauge + kube_ingressclass_annotations{ingressclass="test_ingressclass-labels",annotation_ingressclass_kubernetes_io_is_default_class="true"} 1 + kube_ingressclass_labels{ingressclass="test_ingressclass-labels"} 1 + `, + MetricNames: []string{ + "kube_ingressclass_annotations", "kube_ingressclass_labels", + }, + }, + } + for i, c := range cases { + c.Func = generator.ComposeMetricGenFuncs(ingressClassMetricFamilies(c.AllowAnnotationsList, nil)) + c.Headers = generator.ExtractMetricFamilyHeaders(ingressClassMetricFamilies(c.AllowAnnotationsList, nil)) + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +} diff --git a/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet b/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet index 44ff393ebc..2275c7b584 100644 --- a/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet +++ b/jsonnet/kube-state-metrics/kube-state-metrics.libsonnet @@ -134,6 +134,7 @@ apiGroups: ['networking.k8s.io'], resources: [ 'networkpolicies', + 'ingressclasses', 'ingresses', ], verbs: ['list', 'watch'], diff --git a/tests/manifests/ingress.yaml b/tests/manifests/ingress.yaml index 05428277e1..7e35f7e343 100644 --- a/tests/manifests/ingress.yaml +++ b/tests/manifests/ingress.yaml @@ -1,4 +1,4 @@ -apiVersion: networking.k8s.io/v1 +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: example-ingress diff --git a/tests/manifests/ingressclass.yaml b/tests/manifests/ingressclass.yaml new file mode 100644 index 0000000000..95a5aa1278 --- /dev/null +++ b/tests/manifests/ingressclass.yaml @@ -0,0 +1,8 @@ +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: example-ingressclass + annotations: + ingressclass.kubernetes.io/is-default-class: "true" +spec: + controller: example-ingress/controller