From c18dfa26cf7c2c788e075d3b620e7f178ba7991f Mon Sep 17 00:00:00 2001 From: xieyanker Date: Wed, 5 Jun 2019 11:32:54 +0800 Subject: [PATCH] Add Metrics About StorageClass --- docs/README.md | 1 + docs/storageclass-metrics.md | 8 ++ internal/collector/builder.go | 6 + internal/collector/storageclass.go | 122 ++++++++++++++++++ internal/collector/storageclass_test.go | 121 +++++++++++++++++ .../kube-state-metrics-cluster-role.yaml | 4 + pkg/options/collector.go | 1 + tests/manifests/storageclass.yaml | 7 + vendor/modules.txt | 2 +- 9 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 docs/storageclass-metrics.md create mode 100644 internal/collector/storageclass.go create mode 100644 internal/collector/storageclass_test.go create mode 100644 tests/manifests/storageclass.yaml diff --git a/docs/README.md b/docs/README.md index 8e7d3eb9..194990ac 100644 --- a/docs/README.md +++ b/docs/README.md @@ -60,6 +60,7 @@ Per group of metrics there is one file for each metrics. See each file for speci - [ResourceQuota Metrics](resourcequota-metrics.md) - [Service Metrics](service-metrics.md) - [StatefulSet Metrics](statefulset-metrics.md) +- [StorageClass Metrics](storageclass-metrics.md) - [Namespace Metrics](namespace-metrics.md) - [Horizontal Pod Autoscaler Metrics](horizontalpodautoscaler-metrics.md) - [Endpoint Metrics](endpoint-metrics.md) diff --git a/docs/storageclass-metrics.md b/docs/storageclass-metrics.md new file mode 100644 index 00000000..b3ac5230 --- /dev/null +++ b/docs/storageclass-metrics.md @@ -0,0 +1,8 @@ +# StorageClass Metrics + +| Metric name| Metric type | Labels/tags | Status | +| ---------- | ----------- | ----------- | ----------- | +| kube_storageclass_info | Gauge | `storageclass`=<storageclass-name>
`provisioner`=<storageclass-provisioner>
`reclaimPolicy`=<storageclass-reclaimPolicy>
`volumeBindingMode`=<storageclass-volumeBindingMode> | STABLE | +| kube_storageclass_labels | Gauge | `storageclass`=<storageclass-name>
`label_STORAGECLASS_LABEL`=<STORAGECLASS_LABEL> | STABLE | +| kube_storageclass_created | Gauge | `storageclass`=<storageclass-name> | STABLE | +| kube_storageclass_metadata_resource_version | Gauge | `storageclass`=<storageclass-name>
`resource_version`=<storageclass-resource-version> | STABLE | diff --git a/internal/collector/builder.go b/internal/collector/builder.go index 5c28754d..7b0ee242 100644 --- a/internal/collector/builder.go +++ b/internal/collector/builder.go @@ -31,6 +31,7 @@ import ( v1 "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" policy "k8s.io/api/policy/v1beta1" + storagev1 "k8s.io/api/storage/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" coll "k8s.io/kube-state-metrics/pkg/collector" @@ -135,6 +136,7 @@ var availableCollectors = map[string]func(f *Builder) *coll.Collector{ "secrets": func(b *Builder) *coll.Collector { return b.buildSecretCollector() }, "services": func(b *Builder) *coll.Collector { return b.buildServiceCollector() }, "statefulsets": func(b *Builder) *coll.Collector { return b.buildStatefulSetCollector() }, + "storageclasses": func(b *Builder) *coll.Collector { return b.buildStorageClassCollector() }, } func (b *Builder) buildConfigMapCollector() *coll.Collector { @@ -217,6 +219,10 @@ func (b *Builder) buildStatefulSetCollector() *coll.Collector { return b.buildCollector(statefulSetMetricFamilies, &appsv1.StatefulSet{}, createStatefulSetListWatch) } +func (b *Builder) buildStorageClassCollector() *coll.Collector { + return b.buildCollector(storageClassMetricFamilies, &storagev1.StorageClass{}, createStorageClassListWatch) +} + func (b *Builder) buildPodCollector() *coll.Collector { return b.buildCollector(podMetricFamilies, &v1.Pod{}, createPodListWatch) } diff --git a/internal/collector/storageclass.go b/internal/collector/storageclass.go new file mode 100644 index 00000000..688cdd59 --- /dev/null +++ b/internal/collector/storageclass.go @@ -0,0 +1,122 @@ +/* +Copyright 2019 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 collector + +import ( + "k8s.io/kube-state-metrics/pkg/metric" + + storagev1 "k8s.io/api/storage/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 ( + descStorageClassLabelsName = "kube_storageclass_labels" + descStorageClassLabelsHelp = "Kubernetes labels converted to Prometheus labels." + descStorageClassLabelsDefaultLabels = []string{"storageclass"} + + storageClassMetricFamilies = []metric.FamilyGenerator{ + { + Name: "kube_storageclass_info", + Type: metric.Gauge, + Help: "Information about storageclass.", + GenerateFunc: wrapStorageClassFunc(func(s *storagev1.StorageClass) *metric.Family { + m := metric.Metric{ + LabelKeys: []string{"provisioner", "reclaimPolicy", "volumeBindingMode"}, + LabelValues: []string{s.Provisioner, string(*s.ReclaimPolicy), string(*s.VolumeBindingMode)}, + Value: 1, + } + return &metric.Family{Metrics: []*metric.Metric{&m}} + }), + }, + { + Name: "kube_storageclass_created", + Type: metric.Gauge, + Help: "Unix creation timestamp", + GenerateFunc: wrapStorageClassFunc(func(s *storagev1.StorageClass) *metric.Family { + ms := []*metric.Metric{} + if !s.CreationTimestamp.IsZero() { + ms = append(ms, &metric.Metric{ + Value: float64(s.CreationTimestamp.Unix()), + }) + } + return &metric.Family{ + Metrics: ms, + } + }), + }, + { + Name: "kube_storageclass_metadata_resource_version", + Type: metric.Gauge, + Help: "Resource version representing a specific version of storageclass.", + GenerateFunc: wrapStorageClassFunc(func(s *storagev1.StorageClass) *metric.Family { + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: []string{"resource_version"}, + LabelValues: []string{s.ObjectMeta.ResourceVersion}, + Value: 1, + }, + }, + } + }), + }, + { + Name: descStorageClassLabelsName, + Type: metric.Gauge, + Help: descStorageClassLabelsHelp, + GenerateFunc: wrapStorageClassFunc(func(s *storagev1.StorageClass) *metric.Family { + labelKeys, labelValues := kubeLabelsToPrometheusLabels(s.Labels) + return &metric.Family{ + Metrics: []*metric.Metric{ + { + LabelKeys: labelKeys, + LabelValues: labelValues, + Value: 1, + }, + }, + } + }), + }, + } +) + +func wrapStorageClassFunc(f func(*storagev1.StorageClass) *metric.Family) func(interface{}) *metric.Family { + return func(obj interface{}) *metric.Family { + storageClass := obj.(*storagev1.StorageClass) + + metricFamily := f(storageClass) + + for _, m := range metricFamily.Metrics { + m.LabelKeys = append(descStorageClassLabelsDefaultLabels, m.LabelKeys...) + m.LabelValues = append([]string{storageClass.Name}, m.LabelValues...) + } + + return metricFamily + } +} + +func createStorageClassListWatch(kubeClient clientset.Interface, ns string) cache.ListerWatcher { + return &cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + return kubeClient.StorageV1().StorageClasses().List(opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + return kubeClient.StorageV1().StorageClasses().Watch(opts) + }, + } +} diff --git a/internal/collector/storageclass_test.go b/internal/collector/storageclass_test.go new file mode 100644 index 00000000..aaac6909 --- /dev/null +++ b/internal/collector/storageclass_test.go @@ -0,0 +1,121 @@ +/* +Copyright 2019 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 collector + +import ( + "testing" + + v1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/kube-state-metrics/pkg/metric" +) + +func TestStorageClassCollector(t *testing.T) { + // Fixed metadata on type and help text. We prepend this to every expected + // output so we only have to modify a single place when doing adjustments. + + startTime := 1501569018 + metav1StartTime := metav1.Unix(int64(startTime), 0) + reclaimPolicy := v1.PersistentVolumeReclaimDelete + volumeBindingMode := storagev1.VolumeBindingImmediate + + const metadata = ` + # HELP kube_storageclass_labels Kubernetes labels converted to Prometheus labels. + # TYPE kube_storageclass_labels gauge + # HELP kube_storageclass_info Information about storageclass. + # TYPE kube_storageclass_info gauge + # HELP kube_storageclass_created Unix creation timestamp + # TYPE kube_storageclass_created gauge + # HELP kube_storageclass_metadata_resource_version Resource version representing a specific version of secret. + # TYPE kube_storageclass_metadata_resource_version gauge + ` + cases := []generateMetricsTestCase{ + { + Obj: &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_storageclass-info", + }, + Provisioner: "kubernetes.io/rbd", + ReclaimPolicy: &reclaimPolicy, + VolumeBindingMode: &volumeBindingMode, + }, + Want: ` + kube_storageclass_info{storageclass="test_storageclass-info",provisioner="kubernetes.io/rbd",reclaimPolicy="Delete",volumeBindingMode="Immediate"} 1 + `, + MetricNames: []string{ + "kube_storageclass_info", + }, + }, + { + Obj: &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_kube_storageclass_created", + CreationTimestamp: metav1StartTime, + }, + Provisioner: "kubernetes.io/rbd", + ReclaimPolicy: &reclaimPolicy, + VolumeBindingMode: &volumeBindingMode, + }, + Want: ` + kube_storageclass_created{storageclass="test_kube_storageclass_created"} 1.501569018e+09 + `, + MetricNames: []string{ + "kube_storageclass_created", + }, + }, + { + Obj: &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_kube_storageclass_resource_version", + ResourceVersion: "abcdef", + }, + Provisioner: "kubernetes.io/rbd", + ReclaimPolicy: &reclaimPolicy, + VolumeBindingMode: &volumeBindingMode, + }, + Want: ` + kube_storageclass_metadata_resource_version{storageclass="test_kube_storageclass_resource_version",resource_version="abcdef"} 1 + `, + MetricNames: []string{ + "kube_storageclass_metadata_resource_version", + }, + }, + { + Obj: &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test_storageclass-labels", + Labels: map[string]string{ + "foo": "bar", + }, + }, + Provisioner: "kubernetes.io/rbd", + ReclaimPolicy: &reclaimPolicy, + VolumeBindingMode: &volumeBindingMode, + }, + Want: ` + kube_storageclass_labels{storageclass="test_storageclass-labels",label_foo="bar"} 1 + `, + MetricNames: []string{ + "kube_storageclass_labels", + }, + }, + } + for i, c := range cases { + c.Func = metric.ComposeMetricGenFuncs(storageClassMetricFamilies) + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +} diff --git a/kubernetes/kube-state-metrics-cluster-role.yaml b/kubernetes/kube-state-metrics-cluster-role.yaml index de4099af..b7c57bca 100644 --- a/kubernetes/kube-state-metrics-cluster-role.yaml +++ b/kubernetes/kube-state-metrics-cluster-role.yaml @@ -50,4 +50,8 @@ rules: resources: - certificatesigningrequests verbs: ["list", "watch"] +- apiGroups: ["storage.k8s.io"] + resources: + - storageclasses + verbs: ["list", "watch"] diff --git a/pkg/options/collector.go b/pkg/options/collector.go index 7ecc1751..56345c34 100644 --- a/pkg/options/collector.go +++ b/pkg/options/collector.go @@ -48,5 +48,6 @@ var ( "secrets": struct{}{}, "services": struct{}{}, "statefulsets": struct{}{}, + "storageclasses": struct{}{}, } ) diff --git a/tests/manifests/storageclass.yaml b/tests/manifests/storageclass.yaml new file mode 100644 index 00000000..1a776ca9 --- /dev/null +++ b/tests/manifests/storageclass.yaml @@ -0,0 +1,7 @@ +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: storageclass +provisioner: kubernetes.io/rbd +reclaimPolicy: Delete +volumeBindingMode: Immediate \ No newline at end of file diff --git a/vendor/modules.txt b/vendor/modules.txt index 400bfdfc..1cb4f36f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -126,6 +126,7 @@ k8s.io/api/certificates/v1beta1 k8s.io/api/core/v1 k8s.io/api/extensions/v1beta1 k8s.io/api/policy/v1beta1 +k8s.io/api/storage/v1 k8s.io/api/apps/v1beta1 k8s.io/api/admissionregistration/v1beta1 k8s.io/api/autoscaling/v1 @@ -151,7 +152,6 @@ k8s.io/api/scheduling/v1 k8s.io/api/scheduling/v1alpha1 k8s.io/api/scheduling/v1beta1 k8s.io/api/settings/v1alpha1 -k8s.io/api/storage/v1 k8s.io/api/storage/v1alpha1 k8s.io/api/storage/v1beta1 # k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1