diff --git a/Documentation/README.md b/Documentation/README.md index d4ac4f2096..4bf47fd6c7 100644 --- a/Documentation/README.md +++ b/Documentation/README.md @@ -50,6 +50,7 @@ Per group of metrics there is one file for each metrics. See each file for speci * [PersistentVolume Metrics](persistentvolume-metrics.md) * [PersistentVolumeClaim Metrics](persistentvolumeclaim-metrics.md) * [Pod Metrics](pod-metrics.md) +* [Pod Disruption Budget Metrics](poddisruptionbudget-metrics.md) * [ReplicaSet Metrics](replicaset-metrics.md) * [ReplicationController Metrics](replicationcontroller-metrics.md) * [ResourceQuota Metrics](resourcequota-metrics.md) diff --git a/Documentation/poddisruptionbudget-metrics.md b/Documentation/poddisruptionbudget-metrics.md new file mode 100644 index 0000000000..ae5668f968 --- /dev/null +++ b/Documentation/poddisruptionbudget-metrics.md @@ -0,0 +1,10 @@ +# PodDisruptionBudget Metrics + +| Metric name| Metric type | Labels/tags | Status | +| ---------- | ----------- | ----------- | ----------- | +| kube_poddisruptionbudget_created | Gauge | `poddisruptionbudget`=<pdb-name>
`namespace`=<pdb-namespace> | STABLE +| kube_poddisruptionbudget_status_current_healthy | Gauge | `poddisruptionbudget`=<pdb-name>
`namespace`=<pdb-namespace> | STABLE +| kube_poddisruptionbudget_status_desired_healthy | Gauge | `poddisruptionbudget`=<pdb-name>
`namespace`=<pdb-namespace> | STABLE +| kube_poddisruptionbudget_status_pod_disruptions_allowed | Gauge | `poddisruptionbudget`=<pdb-name>
`namespace`=<pdb-namespace> | STABLE +| kube_poddisruptionbudget_status_expected_pods | Gauge | `poddisruptionbudget`=<pdb-name>
`namespace`=<pdb-namespace> | STABLE +| kube_poddisruptionbudget_status_observed_generation | Gauge | `poddisruptionbudget`=<pdb-name>
`namespace`=<pdb-namespace> | STABLE diff --git a/kubernetes/kube-state-metrics-cluster-role.yaml b/kubernetes/kube-state-metrics-cluster-role.yaml index f9dbb96993..fe227b0b69 100644 --- a/kubernetes/kube-state-metrics-cluster-role.yaml +++ b/kubernetes/kube-state-metrics-cluster-role.yaml @@ -38,3 +38,7 @@ rules: resources: - horizontalpodautoscalers verbs: ["list", "watch"] +- apiGroups: ["policy"] + resources: + - poddisruptionbudgets + verbs: ["list", "watch"] diff --git a/pkg/collectors/builder.go b/pkg/collectors/builder.go index 1afc1ad3e1..a16527445f 100644 --- a/pkg/collectors/builder.go +++ b/pkg/collectors/builder.go @@ -29,6 +29,7 @@ import ( "github.com/golang/glog" "golang.org/x/net/context" "k8s.io/api/core/v1" + "k8s.io/api/policy/v1beta1" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" "k8s.io/kube-state-metrics/pkg/metrics" @@ -106,6 +107,7 @@ var availableCollectors = map[string]func(f *Builder) *Collector{ "nodes": func(b *Builder) *Collector { return b.buildNodeCollector() }, "persistentvolumeclaims": func(b *Builder) *Collector { return b.buildPersistentVolumeClaimCollector() }, "persistentvolumes": func(b *Builder) *Collector { return b.buildPersistentVolumeCollector() }, + "poddisruptionbudgets": func(b *Builder) *Collector { return b.buildPodDisruptionBudgetCollector() }, "pods": func(b *Builder) *Collector { return b.buildPodCollector() }, "replicasets": func(b *Builder) *Collector { return b.buildReplicaSetCollector() }, "replicationcontrollers": func(b *Builder) *Collector { return b.buildReplicationControllerCollector() }, @@ -212,6 +214,13 @@ func (b *Builder) buildPersistentVolumeClaimCollector() *Collector { return newCollector(store) } +func (b *Builder) buildPodDisruptionBudgetCollector() *Collector { + store := metricsstore.NewMetricsStore(generatePodDisruptionBudgetMetrics) + reflectorPerNamespace(b.ctx, b.kubeClient, &v1beta1.PodDisruptionBudget{}, store, b.namespaces, createPodDisruptionBudgetListWatch) + + return newCollector(store) +} + func (b *Builder) buildReplicaSetCollector() *Collector { store := metricsstore.NewMetricsStore(generateReplicaSetMetrics) reflectorPerNamespace(b.ctx, b.kubeClient, &extensions.ReplicaSet{}, store, b.namespaces, createReplicaSetListWatch) diff --git a/pkg/collectors/poddisruptionbudget.go b/pkg/collectors/poddisruptionbudget.go new file mode 100644 index 0000000000..766c30bc6c --- /dev/null +++ b/pkg/collectors/poddisruptionbudget.go @@ -0,0 +1,110 @@ +/* +Copyright 2016 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 collectors + +import ( + "k8s.io/api/policy/v1beta1" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/tools/cache" + "k8s.io/kube-state-metrics/pkg/metrics" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientset "k8s.io/client-go/kubernetes" +) + +var ( + descPodDisruptionBudgetLabelsDefaultLabels = []string{"poddisruptionbudget", "namespace"} + + descPodDisruptionBudgetCreated = newMetricFamilyDef( + "kube_poddisruptionbudget_created", + "Unix creation timestamp", + descPodDisruptionBudgetLabelsDefaultLabels, + nil, + ) + + descPodDisruptionBudgetStatusCurrentHealthy = newMetricFamilyDef( + "kube_poddisruptionbudget_status_current_healthy", + "Current number of healthy pods", + descPodDisruptionBudgetLabelsDefaultLabels, + nil, + ) + descPodDisruptionBudgetStatusDesiredHealthy = newMetricFamilyDef( + "kube_poddisruptionbudget_status_desired_healthy", + "Minimum desired number of healthy pods", + descPodDisruptionBudgetLabelsDefaultLabels, + nil, + ) + descPodDisruptionBudgetStatusPodDisruptionsAllowed = newMetricFamilyDef( + "kube_poddisruptionbudget_status_pod_disruptions_allowed", + "Number of pod disruptions that are currently allowed", + descPodDisruptionBudgetLabelsDefaultLabels, + nil, + ) + descPodDisruptionBudgetStatusExpectedPods = newMetricFamilyDef( + "kube_poddisruptionbudget_status_expected_pods", + "Total number of pods counted by this disruption budget", + descPodDisruptionBudgetLabelsDefaultLabels, + nil, + ) + descPodDisruptionBudgetStatusObservedGeneration = newMetricFamilyDef( + "kube_poddisruptionbudget_status_observed_generation", + "Most recent generation observed when updating this PDB status", + descPodDisruptionBudgetLabelsDefaultLabels, + nil, + ) +) + +func createPodDisruptionBudgetListWatch(kubeClient clientset.Interface, ns string) cache.ListWatch { + return cache.ListWatch{ + ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) { + return kubeClient.PolicyV1beta1().PodDisruptionBudgets(ns).List(opts) + }, + WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) { + return kubeClient.PolicyV1beta1().PodDisruptionBudgets(ns).Watch(opts) + }, + } +} + +func generatePodDisruptionBudgetMetrics(obj interface{}) []*metrics.Metric { + ms := []*metrics.Metric{} + + // TODO: Refactor + pPointer := obj.(*v1beta1.PodDisruptionBudget) + p := *pPointer + + addGauge := func(desc *metricFamilyDef, v float64, lv ...string) { + lv = append([]string{p.Name, p.Namespace}, lv...) + m, err := metrics.NewMetric(desc.Name, desc.LabelKeys, lv, v) + if err != nil { + panic(err) + } + + ms = append(ms, m) + } + + if !p.CreationTimestamp.IsZero() { + addGauge(descPodDisruptionBudgetCreated, float64(p.CreationTimestamp.Unix())) + } + addGauge(descPodDisruptionBudgetStatusCurrentHealthy, float64(p.Status.CurrentHealthy)) + addGauge(descPodDisruptionBudgetStatusDesiredHealthy, float64(p.Status.DesiredHealthy)) + addGauge(descPodDisruptionBudgetStatusPodDisruptionsAllowed, float64(p.Status.PodDisruptionsAllowed)) + addGauge(descPodDisruptionBudgetStatusExpectedPods, float64(p.Status.ExpectedPods)) + addGauge(descPodDisruptionBudgetStatusObservedGeneration, float64(p.Status.ObservedGeneration)) + + return ms +} diff --git a/pkg/collectors/poddisruptionbudget_test.go b/pkg/collectors/poddisruptionbudget_test.go new file mode 100644 index 0000000000..f2b4d86983 --- /dev/null +++ b/pkg/collectors/poddisruptionbudget_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2016 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 collectors + +import ( + "testing" + "time" + + "k8s.io/api/policy/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestPodDisruptionBudgetCollector(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_poddisruptionbudget_created Unix creation timestamp + # TYPE kube_poddisruptionbudget_created gauge + # HELP kube_poddisruptionbudget_status_current_healthy Current number of healthy pods + # TYPE kube_poddisruptionbudget_status_current_healthy gauge + # HELP kube_poddisruptionbudget_status_desired_healthy Minimum desired number of healthy pods + # TYPE kube_poddisruptionbudget_status_desired_healthy gauge + # HELP kube_poddisruptionbudget_status_pod_disruptions_allowed Number of pod disruptions that are currently allowed + # TYPE kube_poddisruptionbudget_status_pod_disruptions_allowed gauge + # HELP kube_poddisruptionbudget_status_expected_pods Total number of pods counted by this disruption budget + # TYPE kube_poddisruptionbudget_status_expected_pods gauge + # HELP kube_poddisruptionbudget_status_observed_generation Most recent generation observed when updating this PDB status + # TYPE kube_poddisruptionbudget_status_observed_generation gauge + ` + cases := []generateMetricsTestCase{ + { + Obj: &v1beta1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pdb1", + CreationTimestamp: metav1.Time{Time: time.Unix(1500000000, 0)}, + Namespace: "ns1", + Generation: 21, + }, + Status: v1beta1.PodDisruptionBudgetStatus{ + CurrentHealthy: 12, + DesiredHealthy: 10, + PodDisruptionsAllowed: 2, + ExpectedPods: 15, + ObservedGeneration: 111, + }, + }, + Want: ` + kube_poddisruptionbudget_created{namespace="ns1",poddisruptionbudget="pdb1"} 1.5e+09 + kube_poddisruptionbudget_status_current_healthy{namespace="ns1",poddisruptionbudget="pdb1"} 12 + kube_poddisruptionbudget_status_desired_healthy{namespace="ns1",poddisruptionbudget="pdb1"} 10 + kube_poddisruptionbudget_status_pod_disruptions_allowed{namespace="ns1",poddisruptionbudget="pdb1"} 2 + kube_poddisruptionbudget_status_expected_pods{namespace="ns1",poddisruptionbudget="pdb1"} 15 + kube_poddisruptionbudget_status_observed_generation{namespace="ns1",poddisruptionbudget="pdb1"} 111 + `, + }, + { + Obj: &v1beta1.PodDisruptionBudget{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pdb2", + Namespace: "ns2", + Generation: 14, + }, + Status: v1beta1.PodDisruptionBudgetStatus{ + CurrentHealthy: 8, + DesiredHealthy: 9, + PodDisruptionsAllowed: 0, + ExpectedPods: 10, + ObservedGeneration: 1111, + }, + }, + Want: ` + kube_poddisruptionbudget_status_current_healthy{namespace="ns2",poddisruptionbudget="pdb2"} 8 + kube_poddisruptionbudget_status_desired_healthy{namespace="ns2",poddisruptionbudget="pdb2"} 9 + kube_poddisruptionbudget_status_pod_disruptions_allowed{namespace="ns2",poddisruptionbudget="pdb2"} 0 + kube_poddisruptionbudget_status_expected_pods{namespace="ns2",poddisruptionbudget="pdb2"} 10 + kube_poddisruptionbudget_status_observed_generation{namespace="ns2",poddisruptionbudget="pdb2"} 1111 + `, + }, + } + for i, c := range cases { + c.Func = generatePodDisruptionBudgetMetrics + if err := c.run(); err != nil { + t.Errorf("unexpected collecting result in %vth run:\n%s", i, err) + } + } +} diff --git a/pkg/options/collector.go b/pkg/options/collector.go index 9860b70596..b00b3f3daa 100644 --- a/pkg/options/collector.go +++ b/pkg/options/collector.go @@ -28,6 +28,7 @@ var ( "limitranges": struct{}{}, "nodes": struct{}{}, "pods": struct{}{}, + "poddisruptionbudgets": struct{}{}, "replicasets": struct{}{}, "replicationcontrollers": struct{}{}, "resourcequotas": struct{}{}, diff --git a/tests/manifests/poddisruptionbudget.yaml b/tests/manifests/poddisruptionbudget.yaml new file mode 100644 index 0000000000..cb980b7b83 --- /dev/null +++ b/tests/manifests/poddisruptionbudget.yaml @@ -0,0 +1,9 @@ +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: pdb +spec: + minAvailable: "50%" + selector: + matchLabels: + name: pdb