Skip to content

Commit 6a24f63

Browse files
author
Julian van den Berkmortel
committed
Add support for the ServiceAccount resource (Ref: #1717)
1 parent 26c8890 commit 6a24f63

File tree

11 files changed

+351
-1
lines changed

11 files changed

+351
-1
lines changed

docs/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ Per group of metrics there is one file for each metrics. See each file for speci
5353
- [ResourceQuota Metrics](resourcequota-metrics.md)
5454
- [Secret Metrics](secret-metrics.md)
5555
- [Service Metrics](service-metrics.md)
56+
- [ServiceAccount Metrics](serviceaccount-metrics.md)
5657
- [StatefulSet Metrics](statefulset-metrics.md)
5758
- [StorageClass Metrics](storageclass-metrics.md)
5859
- [ValidatingWebhookConfiguration Metrics](validatingwebhookconfiguration-metrics.md)

docs/cli-arguments.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Usage of ./kube-state-metrics:
5050
--pod string Name of the pod that contains the kube-state-metrics container. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice.
5151
--pod-namespace string Name of the namespace of the pod specified by --pod. When set, it is expected that --pod and --pod-namespace are both set. Most likely this should be passed via the downward API. This is used for auto-detecting sharding. If set, this has preference over statically configured sharding. This is experimental, it may be removed without notice.
5252
--port int Port to expose metrics on. (default 8080)
53-
--resources string Comma-separated list of Resources to be enabled. Defaults to "certificatesigningrequests,configmaps,cronjobs,daemonsets,deployments,endpoints,horizontalpodautoscalers,ingresses,jobs,leases,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,secrets,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments"
53+
--resources string Comma-separated list of Resources to be enabled. Defaults to "certificatesigningrequests,configmaps,cronjobs,daemonsets,deployments,endpoints,horizontalpodautoscalers,ingresses,jobs,leases,limitranges,mutatingwebhookconfigurations,namespaces,networkpolicies,nodes,persistentvolumeclaims,persistentvolumes,poddisruptionbudgets,pods,replicasets,replicationcontrollers,resourcequotas,secrets,serviceaccounts,services,statefulsets,storageclasses,validatingwebhookconfigurations,volumeattachments"
5454
--shard int32 The instances shard nominal (zero indexed) within the total number of shards. (default 0)
5555
--skip_headers If true, avoid header prefixes in the log messages
5656
--skip_log_headers If true, avoid headers when opening log files

docs/serviceaccount-metrics.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Service Metrics
2+
3+
| Metric name | Metric type | Description | Unit (where applicable) | Labels/tags | Status |
4+
|---------------------------------------|-------------|--------------------------------------------------------------------------------|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
5+
| kube_serviceaccount_info | Gauge | Information about a service account | | `namespace`=&lt;serviceaccount-namespace&gt; <br> `serviceaccount`=&lt;serviceaccount-name&gt; <br> `uid`=&lt;serviceaccount-uid&gt; <br> `automount_token`=&lt;serviceaccount-automount-token&gt; | EXPERIMENTAL |
6+
| kube_serviceaccount_created | Gauge | Unix creation timestamp | | `namespace`=&lt;serviceaccount-namespace&gt; <br> `serviceaccount`=&lt;serviceaccount-name&gt; <br> `uid`=&lt;serviceaccount-uid&gt; | EXPERIMENTAL |
7+
| kube_serviceaccount_deleted | Gauge | Unix deletion timestamp | | `namespace`=&lt;serviceaccount-namespace&gt; <br> `serviceaccount`=&lt;serviceaccount-name&gt; <br> `uid`=&lt;serviceaccount-uid&gt; | EXPERIMENTAL |
8+
| kube_serviceaccount_secret | Gauge | Secret being referenced by a service account | | `namespace`=&lt;serviceaccount-namespace&gt; <br> `serviceaccount`=&lt;serviceaccount-name&gt; <br> `uid`=&lt;serviceaccount-uid&gt; <br> `name`=&lt;secret-name&gt; | EXPERIMENTAL |
9+
| kube_serviceaccount_image_pull_secret | Gauge | Secret being referenced by a service account for the purpose of pulling images | | `namespace`=&lt;serviceaccount-namespace&gt; <br> `serviceaccount`=&lt;serviceaccount-name&gt; <br> `uid`=&lt;serviceaccount-uid&gt; <br> `name`=&lt;secret-name&gt; | EXPERIMENTAL |
10+
| kube_serviceaccount_annotations | Gauge | Kubernetes annotations converted to Prometheus labels | | `namespace`=&lt;serviceaccount-namespace&gt; <br> `serviceaccount`=&lt;serviceaccount-name&gt; <br> `uid`=&lt;serviceaccount-uid&gt; <br> `annotation_SERVICE_ACCOUNT_ANNOTATION`=&lt;SERVICE_ACCOUNT_ANNOTATION&gt; | EXPERIMENTAL |
11+
| kube_serviceaccount_labels | Gauge | Kubernetes labels converted to Prometheus labels | | `namespace`=&lt;serviceaccount-namespace&gt; <br> `serviceaccount`=&lt;serviceaccount-name&gt; <br> `uid`=&lt;serviceaccount-uid&gt; <br> `label_SERVICE_ACCOUNT_LABEL`=&lt;SERVICE_ACCOUNT_LABEL&gt; | EXPERIMENTAL |

examples/autosharding/cluster-role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rules:
1515
- nodes
1616
- pods
1717
- services
18+
- serviceaccounts
1819
- resourcequotas
1920
- replicationcontrollers
2021
- limitranges

examples/standard/cluster-role.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ rules:
1515
- nodes
1616
- pods
1717
- services
18+
- serviceaccounts
1819
- resourcequotas
1920
- replicationcontrollers
2021
- limitranges

internal/store/builder.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ var availableStores = map[string]func(f *Builder) []cache.Store{
283283
"replicationcontrollers": func(b *Builder) []cache.Store { return b.buildReplicationControllerStores() },
284284
"resourcequotas": func(b *Builder) []cache.Store { return b.buildResourceQuotaStores() },
285285
"secrets": func(b *Builder) []cache.Store { return b.buildSecretStores() },
286+
"serviceaccounts": func(b *Builder) []cache.Store { return b.buildServiceAccountStores() },
286287
"services": func(b *Builder) []cache.Store { return b.buildServiceStores() },
287288
"statefulsets": func(b *Builder) []cache.Store { return b.buildStatefulSetStores() },
288289
"storageclasses": func(b *Builder) []cache.Store { return b.buildStorageClassStores() },
@@ -384,6 +385,10 @@ func (b *Builder) buildSecretStores() []cache.Store {
384385
return b.buildStoresFunc(secretMetricFamilies(b.allowAnnotationsList["secrets"], b.allowLabelsList["secrets"]), &v1.Secret{}, createSecretListWatch, b.useAPIServerCache)
385386
}
386387

388+
func (b *Builder) buildServiceAccountStores() []cache.Store {
389+
return b.buildStoresFunc(serviceAccountMetricFamilies(b.allowAnnotationsList["serviceaccounts"], b.allowLabelsList["serviceaccounts"]), &v1.ServiceAccount{}, createServiceAccountListWatch, b.useAPIServerCache)
390+
}
391+
387392
func (b *Builder) buildServiceStores() []cache.Store {
388393
return b.buildStoresFunc(serviceMetricFamilies(b.allowAnnotationsList["services"], b.allowLabelsList["services"]), &v1.Service{}, createServiceListWatch, b.useAPIServerCache)
389394
}

internal/store/serviceaccount.go

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package store
18+
19+
import (
20+
"context"
21+
"strconv"
22+
23+
v1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/apimachinery/pkg/watch"
27+
clientset "k8s.io/client-go/kubernetes"
28+
"k8s.io/client-go/tools/cache"
29+
30+
"k8s.io/kube-state-metrics/v2/pkg/metric"
31+
generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
32+
)
33+
34+
var (
35+
descServiceAccountLabelsDefaultLabels = []string{"namespace", "serviceaccount", "uid"}
36+
)
37+
38+
func serviceAccountMetricFamilies(allowAnnotationsList, allowLabelsList []string) []generator.FamilyGenerator {
39+
return []generator.FamilyGenerator{
40+
createServiceAccountInfoFamilyGenerator(),
41+
createServiceAccountCreatedFamilyGenerator(),
42+
createServiceAccountDeletedFamilyGenerator(),
43+
createServiceAccountSecretFamilyGenerator(),
44+
createServiceAccountImagePullSecretFamilyGenerator(),
45+
createServiceAccountAnnotationsGenerator(allowAnnotationsList),
46+
createServiceAccountLabelsGenerator(allowLabelsList),
47+
}
48+
}
49+
50+
func createServiceAccountInfoFamilyGenerator() generator.FamilyGenerator {
51+
return *generator.NewFamilyGenerator(
52+
"kube_serviceaccount_info",
53+
"Information about a service account",
54+
metric.Gauge,
55+
"",
56+
wrapServiceAccountFunc(func(sa *v1.ServiceAccount) *metric.Family {
57+
var labelKeys []string
58+
var labelValues []string
59+
60+
if sa.AutomountServiceAccountToken != nil {
61+
labelKeys = append(labelKeys, "automount_token")
62+
labelValues = append(labelValues, strconv.FormatBool(*sa.AutomountServiceAccountToken))
63+
}
64+
65+
return &metric.Family{
66+
Metrics: []*metric.Metric{{
67+
LabelKeys: labelKeys,
68+
LabelValues: labelValues,
69+
Value: 1,
70+
}},
71+
}
72+
}),
73+
)
74+
}
75+
76+
func createServiceAccountCreatedFamilyGenerator() generator.FamilyGenerator {
77+
return *generator.NewFamilyGenerator(
78+
"kube_serviceaccount_created",
79+
"Unix creation timestamp",
80+
metric.Gauge,
81+
"",
82+
wrapServiceAccountFunc(func(sa *v1.ServiceAccount) *metric.Family {
83+
var ms []*metric.Metric
84+
85+
if !sa.CreationTimestamp.IsZero() {
86+
ms = append(ms, &metric.Metric{
87+
LabelKeys: []string{},
88+
LabelValues: []string{},
89+
Value: float64(sa.CreationTimestamp.Unix()),
90+
})
91+
}
92+
93+
return &metric.Family{
94+
Metrics: ms,
95+
}
96+
}),
97+
)
98+
}
99+
100+
func createServiceAccountDeletedFamilyGenerator() generator.FamilyGenerator {
101+
return *generator.NewFamilyGenerator(
102+
"kube_serviceaccount_deleted",
103+
"Unix deletion timestamp",
104+
metric.Gauge,
105+
"",
106+
wrapServiceAccountFunc(func(sa *v1.ServiceAccount) *metric.Family {
107+
var ms []*metric.Metric
108+
109+
if sa.DeletionTimestamp != nil && !sa.DeletionTimestamp.IsZero() {
110+
ms = append(ms, &metric.Metric{
111+
LabelKeys: []string{},
112+
LabelValues: []string{},
113+
Value: float64(sa.DeletionTimestamp.Unix()),
114+
})
115+
}
116+
117+
return &metric.Family{
118+
Metrics: ms,
119+
}
120+
}),
121+
)
122+
}
123+
124+
func createServiceAccountSecretFamilyGenerator() generator.FamilyGenerator {
125+
return *generator.NewFamilyGenerator(
126+
"kube_serviceaccount_secret",
127+
"Secret being referenced by a service account",
128+
metric.Gauge,
129+
"",
130+
wrapServiceAccountFunc(func(sa *v1.ServiceAccount) *metric.Family {
131+
var ms []*metric.Metric
132+
133+
for _, s := range sa.Secrets {
134+
ms = append(ms, &metric.Metric{
135+
LabelKeys: []string{"name"},
136+
LabelValues: []string{s.Name},
137+
Value: 1,
138+
})
139+
}
140+
141+
return &metric.Family{
142+
Metrics: ms,
143+
}
144+
}),
145+
)
146+
}
147+
148+
func createServiceAccountImagePullSecretFamilyGenerator() generator.FamilyGenerator {
149+
return *generator.NewFamilyGenerator(
150+
"kube_serviceaccount_image_pull_secret",
151+
"Secret being referenced by a service account for the purpose of pulling images",
152+
metric.Gauge,
153+
"",
154+
wrapServiceAccountFunc(func(sa *v1.ServiceAccount) *metric.Family {
155+
var ms []*metric.Metric
156+
157+
for _, s := range sa.ImagePullSecrets {
158+
ms = append(ms, &metric.Metric{
159+
LabelKeys: []string{"name"},
160+
LabelValues: []string{s.Name},
161+
Value: 1,
162+
})
163+
}
164+
165+
return &metric.Family{
166+
Metrics: ms,
167+
}
168+
}),
169+
)
170+
}
171+
172+
func createServiceAccountAnnotationsGenerator(allowAnnotations []string) generator.FamilyGenerator {
173+
return *generator.NewFamilyGenerator(
174+
"kube_serviceaccount_annotations",
175+
"Kubernetes annotations converted to Prometheus labels.",
176+
metric.Gauge,
177+
"",
178+
wrapServiceAccountFunc(func(sa *v1.ServiceAccount) *metric.Family {
179+
annotationKeys, annotationValues := createPrometheusLabelKeysValues("annotation", sa.Annotations, allowAnnotations)
180+
m := metric.Metric{
181+
LabelKeys: annotationKeys,
182+
LabelValues: annotationValues,
183+
Value: 1,
184+
}
185+
return &metric.Family{
186+
Metrics: []*metric.Metric{&m},
187+
}
188+
}),
189+
)
190+
}
191+
192+
func createServiceAccountLabelsGenerator(allowLabelsList []string) generator.FamilyGenerator {
193+
return *generator.NewFamilyGenerator(
194+
"kube_serviceaccount_labels",
195+
"Kubernetes labels converted to Prometheus labels.",
196+
metric.Gauge,
197+
"",
198+
wrapServiceAccountFunc(func(sa *v1.ServiceAccount) *metric.Family {
199+
labelKeys, labelValues := createPrometheusLabelKeysValues("label", sa.Labels, allowLabelsList)
200+
m := metric.Metric{
201+
LabelKeys: labelKeys,
202+
LabelValues: labelValues,
203+
Value: 1,
204+
}
205+
return &metric.Family{
206+
Metrics: []*metric.Metric{&m},
207+
}
208+
}),
209+
)
210+
}
211+
212+
func wrapServiceAccountFunc(f func(*v1.ServiceAccount) *metric.Family) func(interface{}) *metric.Family {
213+
return func(obj interface{}) *metric.Family {
214+
serviceAccount := obj.(*v1.ServiceAccount)
215+
216+
metricFamily := f(serviceAccount)
217+
218+
for _, m := range metricFamily.Metrics {
219+
m.LabelKeys, m.LabelValues = mergeKeyValues(descServiceAccountLabelsDefaultLabels, []string{serviceAccount.Namespace, serviceAccount.Name, string(serviceAccount.UID)}, m.LabelKeys, m.LabelValues)
220+
}
221+
222+
return metricFamily
223+
}
224+
}
225+
226+
func createServiceAccountListWatch(kubeClient clientset.Interface, ns string, fieldSelector string) cache.ListerWatcher {
227+
return &cache.ListWatch{
228+
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
229+
opts.FieldSelector = fieldSelector
230+
return kubeClient.CoreV1().ServiceAccounts(ns).List(context.TODO(), opts)
231+
},
232+
WatchFunc: func(opts metav1.ListOptions) (watch.Interface, error) {
233+
opts.FieldSelector = fieldSelector
234+
return kubeClient.CoreV1().ServiceAccounts(ns).Watch(context.TODO(), opts)
235+
},
236+
}
237+
}

internal/store/serviceaccount_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
Copyright 2022 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package store
18+
19+
import (
20+
"testing"
21+
"time"
22+
23+
v1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/utils/pointer"
26+
27+
generator "k8s.io/kube-state-metrics/v2/pkg/metric_generator"
28+
)
29+
30+
func TestServiceAccountStore(t *testing.T) {
31+
cases := []generateMetricsTestCase{
32+
{
33+
Obj: &v1.ServiceAccount{
34+
ObjectMeta: metav1.ObjectMeta{
35+
Name: "serviceAccountName",
36+
CreationTimestamp: metav1.Time{Time: time.Unix(1500000000, 0)},
37+
DeletionTimestamp: &metav1.Time{Time: time.Unix(3000000000, 0)},
38+
Namespace: "serviceAccountNS",
39+
UID: "serviceAccountUID",
40+
},
41+
AutomountServiceAccountToken: pointer.Bool(true),
42+
Secrets: []v1.ObjectReference{
43+
{
44+
APIVersion: "v1",
45+
Kind: "Secret",
46+
Name: "secretName",
47+
Namespace: "serviceAccountNS",
48+
},
49+
},
50+
ImagePullSecrets: []v1.LocalObjectReference{
51+
{
52+
Name: "imagePullSecretName",
53+
},
54+
},
55+
},
56+
Want: `
57+
# HELP kube_serviceaccount_info Information about a service account
58+
# HELP kube_serviceaccount_created Unix creation timestamp
59+
# HELP kube_serviceaccount_deleted Unix deletion timestamp
60+
# HELP kube_serviceaccount_secret Secret being referenced by a service account
61+
# HELP kube_serviceaccount_image_pull_secret Secret being referenced by a service account for the purpose of pulling images
62+
# TYPE kube_serviceaccount_info gauge
63+
# TYPE kube_serviceaccount_created gauge
64+
# TYPE kube_serviceaccount_deleted gauge
65+
# TYPE kube_serviceaccount_secret gauge
66+
# TYPE kube_serviceaccount_image_pull_secret gauge
67+
kube_serviceaccount_info{namespace="serviceAccountNS",serviceaccount="serviceAccountName",uid="serviceAccountUID",automount_token="true"} 1
68+
kube_serviceaccount_created{namespace="serviceAccountNS",serviceaccount="serviceAccountName",uid="serviceAccountUID"} 1.5e+09
69+
kube_serviceaccount_deleted{namespace="serviceAccountNS",serviceaccount="serviceAccountName",uid="serviceAccountUID"} 3e+09
70+
kube_serviceaccount_secret{namespace="serviceAccountNS",serviceaccount="serviceAccountName",uid="serviceAccountUID",name="secretName"} 1
71+
kube_serviceaccount_image_pull_secret{namespace="serviceAccountNS",serviceaccount="serviceAccountName",uid="serviceAccountUID",name="imagePullSecretName"} 1`,
72+
MetricNames: []string{
73+
"kube_serviceaccount_info",
74+
"kube_serviceaccount_created",
75+
"kube_serviceaccount_deleted",
76+
"kube_serviceaccount_secret",
77+
"kube_serviceaccount_image_pull_secret",
78+
},
79+
},
80+
}
81+
for i, c := range cases {
82+
c.Func = generator.ComposeMetricGenFuncs(serviceAccountMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList))
83+
c.Headers = generator.ExtractMetricFamilyHeaders(serviceAccountMetricFamilies(c.AllowAnnotationsList, c.AllowLabelsList))
84+
if err := c.run(); err != nil {
85+
t.Errorf("unexpected collecting result in %vth run:\n%s", i, err)
86+
}
87+
}
88+
}

jsonnet/kube-state-metrics/kube-state-metrics.libsonnet

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
'nodes',
5151
'pods',
5252
'services',
53+
'serviceaccounts',
5354
'resourcequotas',
5455
'replicationcontrollers',
5556
'limitranges',

0 commit comments

Comments
 (0)