Skip to content

Commit cd8b87f

Browse files
authored
Merge pull request kubernetes#95562 from wojtek-t/fix_metrics
Fix metrics reporting in kube-apiserver
2 parents a5adff2 + 3d2a806 commit cd8b87f

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

build/visible_to/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ package_group(
436436
"//staging/src/k8s.io/component-base/metrics/...",
437437
"//test/e2e_node",
438438
"//test/integration/apiserver/flowcontrol",
439+
"//test/integration/metrics",
439440
"//vendor/...",
440441
],
441442
)

staging/src/k8s.io/apiserver/pkg/endpoints/installer.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,9 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
594594
requestScope = "resource"
595595
operationSuffix = operationSuffix + "WithPath"
596596
}
597+
if strings.Index(action.Path, "/{name}") != -1 || action.Verb == "POST" {
598+
requestScope = "resource"
599+
}
597600
if action.AllNamespaces {
598601
requestScope = "cluster"
599602
operationSuffix = operationSuffix + "ForAllNamespaces"

test/integration/metrics/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ go_test(
3535
embed = [":go_default_library"],
3636
tags = ["integration"],
3737
deps = [
38+
"//staging/src/k8s.io/api/core/v1:go_default_library",
3839
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
3940
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
4041
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
4142
"//staging/src/k8s.io/client-go/rest:go_default_library",
4243
"//staging/src/k8s.io/component-base/metrics/testutil:go_default_library",
4344
"//test/integration/framework:go_default_library",
45+
"//vendor/github.com/prometheus/common/model:go_default_library",
4446
],
4547
)

test/integration/metrics/metrics_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import (
2525
"runtime"
2626
"testing"
2727

28+
"github.com/prometheus/common/model"
29+
30+
v1 "k8s.io/api/core/v1"
2831
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2932
"k8s.io/apimachinery/pkg/runtime/schema"
3033
clientset "k8s.io/client-go/kubernetes"
@@ -111,3 +114,130 @@ func TestApiserverMetrics(t *testing.T) {
111114
"etcd_request_duration_seconds_sum",
112115
})
113116
}
117+
118+
func TestApiserverMetricsLabels(t *testing.T) {
119+
_, s, closeFn := framework.RunAMaster(nil)
120+
defer closeFn()
121+
122+
client, err := clientset.NewForConfig(&restclient.Config{Host: s.URL, QPS: -1})
123+
if err != nil {
124+
t.Fatalf("Error in create clientset: %v", err)
125+
}
126+
127+
expectedMetrics := []model.Metric{}
128+
129+
metricLabels := func(group, version, resource, subresource, scope, verb string) model.Metric {
130+
return map[model.LabelName]model.LabelValue{
131+
model.LabelName("group"): model.LabelValue(group),
132+
model.LabelName("version"): model.LabelValue(version),
133+
model.LabelName("resource"): model.LabelValue(resource),
134+
model.LabelName("subresource"): model.LabelValue(subresource),
135+
model.LabelName("scope"): model.LabelValue(scope),
136+
model.LabelName("verb"): model.LabelValue(verb),
137+
}
138+
}
139+
140+
callOrDie := func(_ interface{}, err error) {
141+
if err != nil {
142+
t.Fatalf("unexpected error: %v", err)
143+
}
144+
}
145+
146+
appendExpectedMetric := func(metric model.Metric) {
147+
expectedMetrics = append(expectedMetrics, metric)
148+
}
149+
150+
// Call appropriate endpoints to ensure particular metrics will be exposed
151+
152+
// Namespace-scoped resource
153+
c := client.CoreV1().Pods(metav1.NamespaceDefault)
154+
makePod := func(labelValue string) *v1.Pod {
155+
return &v1.Pod{
156+
ObjectMeta: metav1.ObjectMeta{
157+
Name: "foo",
158+
Labels: map[string]string{"foo": labelValue},
159+
},
160+
Spec: v1.PodSpec{
161+
Containers: []v1.Container{
162+
{
163+
Name: "container",
164+
Image: "image",
165+
},
166+
},
167+
},
168+
}
169+
}
170+
171+
callOrDie(c.Create(context.TODO(), makePod("foo"), metav1.CreateOptions{}))
172+
appendExpectedMetric(metricLabels("", "v1", "pods", "", "resource", "POST"))
173+
callOrDie(c.Update(context.TODO(), makePod("bar"), metav1.UpdateOptions{}))
174+
appendExpectedMetric(metricLabels("", "v1", "pods", "", "resource", "PUT"))
175+
callOrDie(c.UpdateStatus(context.TODO(), makePod("bar"), metav1.UpdateOptions{}))
176+
appendExpectedMetric(metricLabels("", "v1", "pods", "status", "resource", "PUT"))
177+
callOrDie(c.Get(context.TODO(), "foo", metav1.GetOptions{}))
178+
appendExpectedMetric(metricLabels("", "v1", "pods", "", "resource", "GET"))
179+
callOrDie(c.List(context.TODO(), metav1.ListOptions{}))
180+
appendExpectedMetric(metricLabels("", "v1", "pods", "", "namespace", "LIST"))
181+
callOrDie(nil, c.Delete(context.TODO(), "foo", metav1.DeleteOptions{}))
182+
appendExpectedMetric(metricLabels("", "v1", "pods", "", "resource", "DELETE"))
183+
// cluster-scoped LIST of namespace-scoped resources
184+
callOrDie(client.CoreV1().Pods(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{}))
185+
appendExpectedMetric(metricLabels("", "v1", "pods", "", "cluster", "LIST"))
186+
187+
// Cluster-scoped resource
188+
cn := client.CoreV1().Namespaces()
189+
makeNamespace := func(labelValue string) *v1.Namespace {
190+
return &v1.Namespace{
191+
ObjectMeta: metav1.ObjectMeta{
192+
Name: "foo",
193+
Labels: map[string]string{"foo": labelValue},
194+
},
195+
}
196+
}
197+
198+
callOrDie(cn.Create(context.TODO(), makeNamespace("foo"), metav1.CreateOptions{}))
199+
appendExpectedMetric(metricLabels("", "v1", "namespaces", "", "resource", "POST"))
200+
callOrDie(cn.Update(context.TODO(), makeNamespace("bar"), metav1.UpdateOptions{}))
201+
appendExpectedMetric(metricLabels("", "v1", "namespaces", "", "resource", "PUT"))
202+
callOrDie(cn.UpdateStatus(context.TODO(), makeNamespace("bar"), metav1.UpdateOptions{}))
203+
appendExpectedMetric(metricLabels("", "v1", "namespaces", "status", "resource", "PUT"))
204+
callOrDie(cn.Get(context.TODO(), "foo", metav1.GetOptions{}))
205+
appendExpectedMetric(metricLabels("", "v1", "namespaces", "", "resource", "GET"))
206+
callOrDie(cn.List(context.TODO(), metav1.ListOptions{}))
207+
appendExpectedMetric(metricLabels("", "v1", "namespaces", "", "cluster", "LIST"))
208+
callOrDie(nil, cn.Delete(context.TODO(), "foo", metav1.DeleteOptions{}))
209+
appendExpectedMetric(metricLabels("", "v1", "namespaces", "", "resource", "DELETE"))
210+
211+
// Verify if all metrics were properly exported.
212+
metrics, err := scrapeMetrics(s)
213+
if err != nil {
214+
t.Fatal(err)
215+
}
216+
217+
samples, ok := metrics["apiserver_request_total"]
218+
if !ok {
219+
t.Fatalf("apiserver_request_total metric not exposed")
220+
}
221+
222+
hasLabels := func(current, expected model.Metric) bool {
223+
for key, value := range expected {
224+
if current[key] != value {
225+
return false
226+
}
227+
}
228+
return true
229+
}
230+
231+
for _, expectedMetric := range expectedMetrics {
232+
found := false
233+
for _, sample := range samples {
234+
if hasLabels(sample.Metric, expectedMetric) {
235+
found = true
236+
break
237+
}
238+
}
239+
if !found {
240+
t.Errorf("No sample found for %#v", expectedMetric)
241+
}
242+
}
243+
}

0 commit comments

Comments
 (0)