diff --git a/README.md b/README.md index 1186ded4..336e65b6 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,18 @@ additional metrics! | kube_replicaset_spec_paused | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> | | kube_replicaset_metadata_generation | Gauge | `replicaset`=<replicaset-name>
`namespace`=<replicaset-namespace> | +### ReplicationController metrics + +| Metric name| Metric type | Labels/tags | +| ---------- | ----------- | ----------- | +| kube_replicationcontroller_status_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> | +| kube_replicationcontroller_status_fully_labeled_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> | +| kube_replicationcontroller_status_ready_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> | +| kube_replicationcontroller_status_available_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> | +| kube_replicationcontroller_status_replicas_observed_generation | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> | +| kube_replicationcontroller_spec_replicas | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> | +| kube_replicationcontroller_metadata_generation | Gauge | `replicationcontroller`=<replicationcontroller-name>
`namespace`=<replicationcontroller-namespace> | + ## kube-state-metrics vs. Heapster [Heapster](https://github.com/kubernetes/heapster) is a project which fetches diff --git a/main.go b/main.go index f642357a..501cb47b 100644 --- a/main.go +++ b/main.go @@ -42,20 +42,22 @@ const ( var ( defaultCollectors = collectorSet{ - "daemonsets": struct{}{}, - "deployments": struct{}{}, - "pods": struct{}{}, - "nodes": struct{}{}, - "resourcequotas": struct{}{}, - "replicasets": struct{}{}, + "daemonsets": struct{}{}, + "deployments": struct{}{}, + "pods": struct{}{}, + "nodes": struct{}{}, + "resourcequotas": struct{}{}, + "replicasets": struct{}{}, + "replicationcontrollers": struct{}{}, } availableCollectors = map[string]func(registry prometheus.Registerer, kubeClient clientset.Interface){ - "daemonsets": RegisterDaemonSetCollector, - "deployments": RegisterDeploymentCollector, - "pods": RegisterPodCollector, - "nodes": RegisterNodeCollector, - "resourcequotas": RegisterResourceQuotaCollector, - "replicasets": RegisterReplicaSetCollector, + "daemonsets": RegisterDaemonSetCollector, + "deployments": RegisterDeploymentCollector, + "pods": RegisterPodCollector, + "nodes": RegisterNodeCollector, + "resourcequotas": RegisterResourceQuotaCollector, + "replicasets": RegisterReplicaSetCollector, + "replicationcontrollers": RegisterReplicationControllerCollector, } ) diff --git a/replicationcontroller.go b/replicationcontroller.go new file mode 100644 index 00000000..35d1b219 --- /dev/null +++ b/replicationcontroller.go @@ -0,0 +1,133 @@ +/* +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 main + +import ( + "context" + + "github.com/golang/glog" + "github.com/prometheus/client_golang/prometheus" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/pkg/api" + "k8s.io/client-go/pkg/api/v1" + "k8s.io/client-go/tools/cache" +) + +var ( + descReplicationControllerStatusReplicas = prometheus.NewDesc( + "kube_replicationcontroller_status_replicas", + "The number of replicas per ReplicationController.", + []string{"namespace", "replicationcontroller"}, nil, + ) + descReplicationControllerStatusFullyLabeledReplicas = prometheus.NewDesc( + "kube_replicationcontroller_status_fully_labeled_replicas", + "The number of fully labeled replicas per ReplicationController.", + []string{"namespace", "replicationcontroller"}, nil, + ) + descReplicationControllerStatusReadyReplicas = prometheus.NewDesc( + "kube_replicationcontroller_status_ready_replicas", + "The number of ready replicas per ReplicationController.", + []string{"namespace", "replicationcontroller"}, nil, + ) + descReplicationControllerStatusAvailableReplicas = prometheus.NewDesc( + "kube_replicationcontroller_status_available_replicas", + "The number of available replicas per ReplicationController.", + []string{"namespace", "replicationcontroller"}, nil, + ) + descReplicationControllerStatusObservedGeneration = prometheus.NewDesc( + "kube_replicationcontroller_status_observed_generation", + "The generation observed by the ReplicationController controller.", + []string{"namespace", "replicationcontroller"}, nil, + ) + descReplicationControllerSpecReplicas = prometheus.NewDesc( + "kube_replicationcontroller_spec_replicas", + "Number of desired pods for a ReplicationController.", + []string{"namespace", "replicationcontroller"}, nil, + ) + descReplicationControllerMetadataGeneration = prometheus.NewDesc( + "kube_replicationcontroller_metadata_generation", + "Sequence number representing a specific generation of the desired state.", + []string{"namespace", "replicationcontroller"}, nil, + ) +) + +type ReplicationControllerLister func() ([]v1.ReplicationController, error) + +func (l ReplicationControllerLister) List() ([]v1.ReplicationController, error) { + return l() +} + +func RegisterReplicationControllerCollector(registry prometheus.Registerer, kubeClient kubernetes.Interface) { + client := kubeClient.Extensions().RESTClient() + rclw := cache.NewListWatchFromClient(client, "replicationcontrollers", api.NamespaceAll, nil) + rcinf := cache.NewSharedInformer(rclw, &v1.ReplicationController{}, resyncPeriod) + + replicationControllerLister := ReplicationControllerLister(func() (rcs []v1.ReplicationController, err error) { + for _, c := range rcinf.GetStore().List() { + rcs = append(rcs, *(c.(*v1.ReplicationController))) + } + return rcs, nil + }) + + registry.MustRegister(&replicationcontrollerCollector{store: replicationControllerLister}) + go rcinf.Run(context.Background().Done()) +} + +type replicationcontrollerStore interface { + List() (replicationcontrollers []v1.ReplicationController, err error) +} + +type replicationcontrollerCollector struct { + store replicationcontrollerStore +} + +// Describe implements the prometheus.Collector interface. +func (dc *replicationcontrollerCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- descReplicationControllerStatusReplicas + ch <- descReplicationControllerStatusFullyLabeledReplicas + ch <- descReplicationControllerStatusReadyReplicas + ch <- descReplicationControllerStatusAvailableReplicas + ch <- descReplicationControllerStatusObservedGeneration + ch <- descReplicationControllerSpecReplicas + ch <- descReplicationControllerMetadataGeneration +} + +// Collect implements the prometheus.Collector interface. +func (dc *replicationcontrollerCollector) Collect(ch chan<- prometheus.Metric) { + dpls, err := dc.store.List() + if err != nil { + glog.Errorf("listing replicationcontrollers failed: %s", err) + return + } + for _, d := range dpls { + dc.collectReplicationController(ch, d) + } +} + +func (dc *replicationcontrollerCollector) collectReplicationController(ch chan<- prometheus.Metric, d v1.ReplicationController) { + addGauge := func(desc *prometheus.Desc, v float64, lv ...string) { + lv = append([]string{d.Namespace, d.Name}, lv...) + ch <- prometheus.MustNewConstMetric(desc, prometheus.GaugeValue, v, lv...) + } + addGauge(descReplicationControllerStatusReplicas, float64(d.Status.Replicas)) + addGauge(descReplicationControllerStatusFullyLabeledReplicas, float64(d.Status.FullyLabeledReplicas)) + addGauge(descReplicationControllerStatusReadyReplicas, float64(d.Status.ReadyReplicas)) + addGauge(descReplicationControllerStatusAvailableReplicas, float64(d.Status.AvailableReplicas)) + addGauge(descReplicationControllerStatusObservedGeneration, float64(d.Status.ObservedGeneration)) + addGauge(descReplicationControllerSpecReplicas, float64(*d.Spec.Replicas)) + addGauge(descReplicationControllerMetadataGeneration, float64(d.ObjectMeta.Generation)) +} diff --git a/replicationcontroller_test.go b/replicationcontroller_test.go new file mode 100644 index 00000000..9c149c5d --- /dev/null +++ b/replicationcontroller_test.go @@ -0,0 +1,125 @@ +/* +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 main + +import ( + "testing" + + "k8s.io/client-go/pkg/api/v1" +) + +var ( + rc1Replicas int32 = 5 + rc2Replicas int32 = 0 +) + +type mockReplicationControllerStore struct { + f func() ([]v1.ReplicationController, error) +} + +func (rs mockReplicationControllerStore) List() (replicationcontrollers []v1.ReplicationController, err error) { + return rs.f() +} + +func TestReplicationControllerCollector(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_replicationcontroller_metadata_generation Sequence number representing a specific generation of the desired state. + # TYPE kube_replicationcontroller_metadata_generation gauge + # HELP kube_replicationcontroller_status_replicas The number of replicas per ReplicationController. + # TYPE kube_replicationcontroller_status_replicas gauge + # HELP kube_replicationcontroller_status_fully_labeled_replicas The number of fully labeled replicas per ReplicationController. + # TYPE kube_replicationcontroller_status_fully_labeled_replicas gauge + # HELP kube_replicationcontroller_status_available_replicas The number of available replicas per ReplicationController. + # TYPE kube_replicationcontroller_status_available_replicas gauge + # HELP kube_replicationcontroller_status_ready_replicas The number of ready replicas per ReplicationController. + # TYPE kube_replicationcontroller_status_ready_replicas gauge + # HELP kube_replicationcontroller_status_observed_generation The generation observed by the ReplicationController controller. + # TYPE kube_replicationcontroller_status_observed_generation gauge + # HELP kube_replicationcontroller_spec_replicas Number of desired pods for a ReplicationController. + # TYPE kube_replicationcontroller_spec_replicas gauge + ` + cases := []struct { + rss []v1.ReplicationController + want string + }{ + { + rss: []v1.ReplicationController{ + { + ObjectMeta: v1.ObjectMeta{ + Name: "rc1", + Namespace: "ns1", + Generation: 21, + }, + Status: v1.ReplicationControllerStatus{ + Replicas: 5, + FullyLabeledReplicas: 10, + ReadyReplicas: 5, + AvailableReplicas: 3, + ObservedGeneration: 1, + }, + Spec: v1.ReplicationControllerSpec{ + Replicas: &rc1Replicas, + }, + }, { + ObjectMeta: v1.ObjectMeta{ + Name: "rc2", + Namespace: "ns2", + Generation: 14, + }, + Status: v1.ReplicationControllerStatus{ + Replicas: 0, + FullyLabeledReplicas: 5, + ReadyReplicas: 0, + AvailableReplicas: 0, + ObservedGeneration: 5, + }, + Spec: v1.ReplicationControllerSpec{ + Replicas: &rc2Replicas, + }, + }, + }, + want: metadata + ` + kube_replicationcontroller_metadata_generation{namespace="ns1",replicationcontroller="rc1"} 21 + kube_replicationcontroller_metadata_generation{namespace="ns2",replicationcontroller="rc2"} 14 + kube_replicationcontroller_status_replicas{namespace="ns1",replicationcontroller="rc1"} 5 + kube_replicationcontroller_status_replicas{namespace="ns2",replicationcontroller="rc2"} 0 + kube_replicationcontroller_status_observed_generation{namespace="ns1",replicationcontroller="rc1"} 1 + kube_replicationcontroller_status_observed_generation{namespace="ns2",replicationcontroller="rc2"} 5 + kube_replicationcontroller_status_fully_labeled_replicas{namespace="ns1",replicationcontroller="rc1"} 10 + kube_replicationcontroller_status_fully_labeled_replicas{namespace="ns2",replicationcontroller="rc2"} 5 + kube_replicationcontroller_status_ready_replicas{namespace="ns1",replicationcontroller="rc1"} 5 + kube_replicationcontroller_status_ready_replicas{namespace="ns2",replicationcontroller="rc2"} 0 + kube_replicationcontroller_status_available_replicas{namespace="ns1",replicationcontroller="rc1"} 3 + kube_replicationcontroller_status_available_replicas{namespace="ns2",replicationcontroller="rc2"} 0 + kube_replicationcontroller_spec_replicas{namespace="ns1",replicationcontroller="rc1"} 5 + kube_replicationcontroller_spec_replicas{namespace="ns2",replicationcontroller="rc2"} 0 + `, + }, + } + for _, c := range cases { + dc := &replicationcontrollerCollector{ + store: mockReplicationControllerStore{ + f: func() ([]v1.ReplicationController, error) { return c.rss, nil }, + }, + } + if err := gatherAndCompare(dc, c.want, nil); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + } +}