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