/
metrics.go
143 lines (129 loc) · 4.77 KB
/
metrics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Copyright 2019 The Operator-SDK Authors
//
// 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 kubemetrics
import (
"errors"
"fmt"
"strings"
"github.com/operator-framework/operator-sdk/pkg/k8sutil"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/rest"
ksmetric "k8s.io/kube-state-metrics/pkg/metric"
metricsstore "k8s.io/kube-state-metrics/pkg/metrics_store"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)
var log = logf.Log.WithName("kubemetrics")
// GenerateAndServeCRMetrics generates CustomResource specific metrics for each custom resource GVK in operatorGVKs.
// A list of namespaces, ns, can be passed to ServeCRMetrics to scope the generated metrics. Passing nil or
// an empty list of namespaces will result in an error.
// The function also starts serving the generated collections of the metrics on given host and port.
func GenerateAndServeCRMetrics(cfg *rest.Config,
ns []string,
operatorGVKs []schema.GroupVersionKind,
host string, port int32) error {
// We have to have at least one namespace.
if len(ns) < 1 {
return errors.New(
"namespaces were empty; pass at least one namespace to generate custom resource metrics")
}
// Create new unstructured client.
var allStores [][]*metricsstore.MetricsStore
log.V(1).Info("Starting collecting operator types")
// Loop through all the possible operator/custom resource specific types.
for _, gvk := range operatorGVKs {
apiVersion := gvk.GroupVersion().String()
kind := gvk.Kind
// Generate metric based on the kind.
metricFamilies := generateMetricFamilies(gvk.Kind)
log.V(1).Info("Generating metric families", "apiVersion", apiVersion, "kind", kind)
dclient, err := newClientForGVK(cfg, apiVersion, kind)
if err != nil {
return err
}
namespaced, err := isNamespaced(gvk, cfg)
if err != nil {
return err
}
var gvkStores []*metricsstore.MetricsStore
if namespaced {
gvkStores = NewNamespacedMetricsStores(dclient, ns, apiVersion, kind, metricFamilies)
} else {
gvkStores = NewClusterScopedMetricsStores(dclient, apiVersion, kind, metricFamilies)
}
// Generate collector based on the group/version, kind and the metric families.
allStores = append(allStores, gvkStores)
}
// Start serving metrics.
log.V(1).Info("Starting serving custom resource metrics")
go ServeMetrics(allStores, host, port)
return nil
}
func generateMetricFamilies(kind string) []ksmetric.FamilyGenerator {
helpText := fmt.Sprintf("Information about the %s custom resource.", kind)
kindName := strings.ToLower(kind)
metricName := fmt.Sprintf("%s_info", kindName)
return []ksmetric.FamilyGenerator{
ksmetric.FamilyGenerator{
Name: metricName,
Type: ksmetric.Gauge,
Help: helpText,
GenerateFunc: func(obj interface{}) *ksmetric.Family {
crd := obj.(*unstructured.Unstructured)
return &ksmetric.Family{
Metrics: []*ksmetric.Metric{
{
Value: 1,
LabelKeys: []string{"namespace", kindName},
LabelValues: []string{crd.GetNamespace(), crd.GetName()},
},
},
}
},
},
}
}
// GetNamespacesForMetrics wil return all namespaces which will be used to export the metrics
func GetNamespacesForMetrics(operatorNs string) ([]string, error) {
ns := []string{operatorNs}
// Get the value from WATCH_NAMESPACES
watchNamespace, err := k8sutil.GetWatchNamespace()
if err != nil {
return nil, err
}
// Generate metrics from the WATCH_NAMESPACES value if it contains multiple namespaces
if strings.Contains(watchNamespace, ",") {
ns = strings.Split(watchNamespace, ",")
}
return ns, nil
}
func isNamespaced(gvk schema.GroupVersionKind, cfg *rest.Config) (bool, error) {
discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg)
if err != nil {
log.Error(err, "Unable to get discovery client")
return false, err
}
resourceList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())
if err != nil {
log.Error(err, "Unable to get resource list for", "apiversion", gvk.GroupVersion().String())
return false, err
}
for _, apiResource := range resourceList.APIResources {
if apiResource.Kind == gvk.Kind {
return apiResource.Namespaced, nil
}
}
return false, errors.New("unable to find type: " + gvk.String() + " in server")
}