From fe6a64febac705e1cfec27a17ff1cc768d27c495 Mon Sep 17 00:00:00 2001 From: Stijn De Haes Date: Sun, 18 Jun 2017 20:47:43 +0200 Subject: [PATCH] Added StatefulSet metrics --- Documentation/statefulset-metrics.md | 8 +++ collectors/statefulset.go | 98 ++++++++++++++++++++++++++++ collectors/statefulset_test.go | 96 +++++++++++++++++++++++++++ main.go | 2 + 4 files changed, 204 insertions(+) create mode 100644 Documentation/statefulset-metrics.md create mode 100644 collectors/statefulset.go create mode 100644 collectors/statefulset_test.go diff --git a/Documentation/statefulset-metrics.md b/Documentation/statefulset-metrics.md new file mode 100644 index 00000000..f53636a3 --- /dev/null +++ b/Documentation/statefulset-metrics.md @@ -0,0 +1,8 @@ +# Service Metrics + +| Metric name| Metric type | Labels/tags | +| ---------- | ----------- | ----------- | +| kube_statefulset_status_replicas | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | +| kube_statefulset_status_observed_generation | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | +| kube_statefulset_replicas | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | +| kube_statefulset_metadata_generation | Gauge | `statefulset`=<statefulset-name>
`namespace`=<statefulset-namespace> | \ No newline at end of file diff --git a/collectors/statefulset.go b/collectors/statefulset.go new file mode 100644 index 00000000..9f0e300e --- /dev/null +++ b/collectors/statefulset.go @@ -0,0 +1,98 @@ +package collectors + +import ( + "github.com/prometheus/client_golang/prometheus" + "k8s.io/client-go/pkg/apis/apps/v1beta1" + "github.com/golang/glog" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "golang.org/x/net/context" + "k8s.io/client-go/pkg/api" +) + +var ( + descStatefulSetStatusReplicas = prometheus.NewDesc( + "kube_statefulset_status_replicas", + "The number of replicas per StatefulSet.", + []string{"namespace", "statefulset"}, nil, + ) + + descStatefulSetStatusObservedGeneration = prometheus.NewDesc( + "kube_statefulset_status_observed_generation", + "The generation observed by the StatefulSet controller.", + []string{"namespace", "statefulset"}, nil, + ) + + descStatefulSetSpecReplicas = prometheus.NewDesc( + "kube_statefulset_replicas", + "Number of desired pods for a StatefulSet.", + []string{"namespace", "statefulset"}, nil, + ) + + descStatefulSetMetadataGeneration = prometheus.NewDesc( + "kube_statefulset_metadata_generation", + "Sequence number representing a specific generation of the desired state for the StatefulSet.", + []string{"namespace", "statefulset"}, nil, + ) +) + +type StatefulSetLister func() ([]v1beta1.StatefulSet, error) + +func (l StatefulSetLister) List() ([]v1beta1.StatefulSet, error) { + return l() +} + +func RegisterStatefulSetCollector(registry prometheus.Registerer, kubeClient kubernetes.Interface) { + client := kubeClient.AppsV1beta1().RESTClient() + dlw := cache.NewListWatchFromClient(client, "statefulsets", api.NamespaceAll, nil) + dinf := cache.NewSharedInformer(dlw, &v1beta1.StatefulSet{}, resyncPeriod) + + statefulSetLister := StatefulSetLister(func() (statefulSets []v1beta1.StatefulSet, err error) { + for _, c := range dinf.GetStore().List() { + statefulSets = append(statefulSets, *(c.(*v1beta1.StatefulSet))) + } + return statefulSets, nil + }) + + registry.MustRegister(&statefulSetCollector{store: statefulSetLister}) + go dinf.Run(context.Background().Done()) +} + +type statefulSetStore interface { + List() (statefulSets []v1beta1.StatefulSet, err error) +} + +type statefulSetCollector struct { + store statefulSetStore +} + +// Describe implements the prometheus.Collector interface. +func (dc *statefulSetCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- descStatefulSetStatusReplicas + ch <- descStatefulSetStatusObservedGeneration + ch <- descStatefulSetSpecReplicas + ch <- descStatefulSetMetadataGeneration +} + +// Collect implements the prometheus.Collector interface. +func (sc *statefulSetCollector) Collect(ch chan<- prometheus.Metric) { + dpls, err := sc.store.List() + if err != nil { + glog.Errorf("listing statefulsets failed: %s", err) + return + } + for _, d := range dpls { + sc.collectStatefulSet(ch, d) + } +} + +func (dc *statefulSetCollector) collectStatefulSet(ch chan<- prometheus.Metric, statefulSet v1beta1.StatefulSet) { + addGauge := func(desc *prometheus.Desc, v float64, lv ...string) { + lv = append([]string{statefulSet.Namespace, statefulSet.Name}, lv...) + ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, v, lv...) + } + addGauge(descStatefulSetStatusReplicas, float64(statefulSet.Status.Replicas)) + addGauge(descStatefulSetStatusObservedGeneration, float64(*statefulSet.Status.ObservedGeneration)) + addGauge(descStatefulSetSpecReplicas, float64(*statefulSet.Spec.Replicas)) + addGauge(descStatefulSetMetadataGeneration, float64(statefulSet.ObjectMeta.Generation)) +} \ No newline at end of file diff --git a/collectors/statefulset_test.go b/collectors/statefulset_test.go new file mode 100644 index 00000000..d92dcab6 --- /dev/null +++ b/collectors/statefulset_test.go @@ -0,0 +1,96 @@ +package collectors + +import ( + "k8s.io/client-go/pkg/apis/apps/v1beta1" + "testing" + "k8s.io/client-go/pkg/api/v1" +) + +var ( + statefulSet1Replicas int32 = 3 + statefulSet2Replicas int32 = 6 + + statefulSet1ObservedGeneration int64 = 1 + statefulSet2ObservedGeneration int64 = 2 +) + +type mockStatefulSetStore struct { + f func() ([]v1beta1.StatefulSet, error) +} + +func (ds mockStatefulSetStore) List() (deployments []v1beta1.StatefulSet, err error) { + return ds.f() +} + +func TestStatefuleSetCollector(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. + const metadata = ` + # HELP kube_statefulset_status_replicas The number of replicas per StatefulSet. + # TYPE kube_statefulset_status_replicas gauge + # HELP kube_statefulset_status_observed_generation The generation observed by the StatefulSet controller. + # TYPE kube_statefulset_status_observed_generation gauge + # HELP kube_statefulset_replicas Number of desired pods for a StatefulSet. + # TYPE kube_statefulset_replicas gauge + # HELP kube_statefulset_metadata_generation Sequence number representing a specific generation of the desired state for the StatefulSet. + # TYPE kube_statefulset_metadata_generation gauge + ` + cases := []struct { + depls []v1beta1.StatefulSet + want string + }{ + { + depls: []v1beta1.StatefulSet{ + { + ObjectMeta: v1.ObjectMeta{ + Name: "statefulset1", + Namespace: "ns1", + Generation: 3, + }, + Spec: v1beta1.StatefulSetSpec{ + Replicas: &statefulSet1Replicas, + ServiceName: "statefulset1service", + }, + Status: v1beta1.StatefulSetStatus{ + ObservedGeneration: &statefulSet1ObservedGeneration, + Replicas: 2, + }, + }, { + ObjectMeta: v1.ObjectMeta{ + Name: "statefulset2", + Namespace: "ns2", + Generation: 21, + }, + Spec: v1beta1.StatefulSetSpec{ + Replicas: &statefulSet2Replicas, + ServiceName: "statefulset2service", + }, + Status: v1beta1.StatefulSetStatus{ + ObservedGeneration: &statefulSet2ObservedGeneration, + Replicas: 5, + }, + }, + }, + want: metadata + ` + kube_statefulset_status_replicas{namespace="ns1",statefulset="statefulset1"} 2 + kube_statefulset_status_replicas{namespace="ns2",statefulset="statefulset2"} 5 + kube_statefulset_status_observed_generation{namespace="ns1",statefulset="statefulset1"} 1 + kube_statefulset_status_observed_generation{namespace="ns2",statefulset="statefulset2"} 2 + kube_statefulset_replicas{namespace="ns1",statefulset="statefulset1"} 3 + kube_statefulset_replicas{namespace="ns2",statefulset="statefulset2"} 6 + kube_statefulset_metadata_generation{namespace="ns1",statefulset="statefulset1"} 3 + kube_statefulset_metadata_generation{namespace="ns2",statefulset="statefulset2"} 21 + `, + }, + } + for _, c := range cases { + sc := &statefulSetCollector{ + store: mockStatefulSetStore{ + f: func() ([]v1beta1.StatefulSet, error) { return c.depls, nil }, + }, + } + if err := gatherAndCompare(sc, c.want, nil); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + } +} \ No newline at end of file diff --git a/main.go b/main.go index e751e763..4fac69d8 100644 --- a/main.go +++ b/main.go @@ -54,6 +54,7 @@ var ( "services": struct{}{}, "jobs": struct{}{}, "cronjobs": struct{}{}, + "statefulsets": struct{}{}, } availableCollectors = map[string]func(registry prometheus.Registerer, kubeClient clientset.Interface){ "cronjobs": collectors.RegisterCronJobCollector, @@ -67,6 +68,7 @@ var ( "replicationcontrollers": collectors.RegisterReplicationControllerCollector, "resourcequotas": collectors.RegisterResourceQuotaCollector, "services": collectors.RegisterServiceCollector, + "statefulsets": collectors.RegisterStatefulSetCollector, } )