Skip to content

Commit ff81845

Browse files
author
Mengqi Yu
committed
add metrics for webhook
1 parent 86ad6a3 commit ff81845

File tree

6 files changed

+100
-15
lines changed

6 files changed

+100
-15
lines changed

example/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ func main() {
108108

109109
entryLog.Info("setting up webhook server")
110110
as, err := webhook.NewServer("foo-admission-server", mgr, webhook.ServerOptions{
111-
Port: 9876,
112-
CertDir: "/tmp/cert",
111+
Port: 9876,
112+
CertDir: "/tmp/cert",
113113
DisableWebhookConfigInstaller: &disableWebhookConfigInstaller,
114114
BootstrapOptions: &webhook.BootstrapOptions{
115115
Secret: &apitypes.NamespacedName{

pkg/webhook/admission/http.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ import (
2424
"io"
2525
"io/ioutil"
2626
"net/http"
27+
"time"
2728

2829
"k8s.io/api/admission/v1beta1"
2930
admissionv1beta1 "k8s.io/api/admission/v1beta1"
3031
"k8s.io/apimachinery/pkg/runtime"
3132
"k8s.io/apimachinery/pkg/runtime/serializer"
3233
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3334
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
35+
"sigs.k8s.io/controller-runtime/pkg/webhook/internal/metrics"
3436
)
3537

3638
var admissionv1beta1scheme = runtime.NewScheme()
@@ -47,6 +49,9 @@ func addToScheme(scheme *runtime.Scheme) {
4749
var _ http.Handler = &Webhook{}
4850

4951
func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
52+
startTS := time.Now()
53+
defer metrics.Duration.WithLabelValues(wh.Name).Observe(time.Now().Sub(startTS).Seconds())
54+
5055
var body []byte
5156
var err error
5257

@@ -55,14 +60,14 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5560
if body, err = ioutil.ReadAll(r.Body); err != nil {
5661
log.Error(err, "unable to read the body from the incoming request")
5762
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
58-
writeResponse(w, reviewResponse)
63+
wh.writeResponse(w, reviewResponse)
5964
return
6065
}
6166
} else {
6267
err = errors.New("request body is empty")
6368
log.Error(err, "bad request")
6469
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
65-
writeResponse(w, reviewResponse)
70+
wh.writeResponse(w, reviewResponse)
6671
return
6772
}
6873

@@ -72,31 +77,39 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
7277
err = fmt.Errorf("contentType=%s, expect application/json", contentType)
7378
log.Error(err, "unable to process a request with an unknown content type")
7479
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
75-
writeResponse(w, reviewResponse)
80+
wh.writeResponse(w, reviewResponse)
7681
return
7782
}
7883

7984
ar := v1beta1.AdmissionReview{}
8085
if _, _, err := admissionv1beta1schemecodecs.UniversalDeserializer().Decode(body, nil, &ar); err != nil {
8186
log.Error(err, "unable to decode the request")
8287
reviewResponse = ErrorResponse(http.StatusBadRequest, err)
83-
writeResponse(w, reviewResponse)
88+
wh.writeResponse(w, reviewResponse)
8489
return
8590
}
8691

8792
// TODO: add panic-recovery for Handle
8893
reviewResponse = wh.Handle(context.Background(), types.Request{AdmissionRequest: ar.Request})
89-
writeResponse(w, reviewResponse)
94+
wh.writeResponse(w, reviewResponse)
9095
}
9196

92-
func writeResponse(w io.Writer, response types.Response) {
97+
func (wh *Webhook) writeResponse(w io.Writer, response types.Response) {
98+
if response.Response.Result.Code != 0 {
99+
if response.Response.Result.Code == http.StatusOK {
100+
metrics.TotalRequests.WithLabelValues(wh.Name, "true").Inc()
101+
} else {
102+
metrics.TotalRequests.WithLabelValues(wh.Name, "false").Inc()
103+
}
104+
}
105+
93106
encoder := json.NewEncoder(w)
94107
responseAdmissionReview := v1beta1.AdmissionReview{
95108
Response: response.Response,
96109
}
97110
err := encoder.Encode(responseAdmissionReview)
98111
if err != nil {
99112
log.Error(err, "unable to encode the response")
100-
writeResponse(w, ErrorResponse(http.StatusInternalServerError, err))
113+
wh.writeResponse(w, ErrorResponse(http.StatusInternalServerError, err))
101114
}
102115
}

pkg/webhook/admission/http_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ var _ = Describe("admission webhook http handler", func() {
134134
Type: types.WebhookTypeValidating,
135135
Handlers: []Handler{h},
136136
}
137-
expected := []byte(`{"response":{"uid":"","allowed":true}}
137+
expected := []byte(`{"response":{"uid":"","allowed":true,"status":{"metadata":{},"code":200}}}
138138
`)
139139
It("should return a response successfully", func() {
140140
wh.ServeHTTP(w, req)

pkg/webhook/admission/webhook.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ func (w *Webhook) handleMutating(ctx context.Context, req atypes.Request) atypes
132132
for _, handler := range w.Handlers {
133133
resp := handler.Handle(ctx, req)
134134
if !resp.Response.Allowed {
135+
setStatusOKInAdmissionResponse(resp.Response)
135136
return resp
136137
}
137138
if resp.Response.PatchType != nil && *resp.Response.PatchType != admissionv1beta1.PatchTypeJSONPatch {
@@ -148,7 +149,10 @@ func (w *Webhook) handleMutating(ctx context.Context, req atypes.Request) atypes
148149
}
149150
return atypes.Response{
150151
Response: &admissionv1beta1.AdmissionResponse{
151-
Allowed: true,
152+
Allowed: true,
153+
Result: &metav1.Status{
154+
Code: http.StatusOK,
155+
},
152156
Patch: marshaledPatch,
153157
PatchType: func() *admissionv1beta1.PatchType { pt := admissionv1beta1.PatchTypeJSONPatch; return &pt }(),
154158
},
@@ -159,16 +163,32 @@ func (w *Webhook) handleValidating(ctx context.Context, req atypes.Request) atyp
159163
for _, handler := range w.Handlers {
160164
resp := handler.Handle(ctx, req)
161165
if !resp.Response.Allowed {
166+
setStatusOKInAdmissionResponse(resp.Response)
162167
return resp
163168
}
164169
}
165170
return atypes.Response{
166171
Response: &admissionv1beta1.AdmissionResponse{
167172
Allowed: true,
173+
Result: &metav1.Status{
174+
Code: http.StatusOK,
175+
},
168176
},
169177
}
170178
}
171179

180+
func setStatusOKInAdmissionResponse(resp *admissionv1beta1.AdmissionResponse) {
181+
if resp == nil {
182+
return
183+
}
184+
if resp.Result == nil {
185+
resp.Result = &metav1.Status{}
186+
}
187+
if resp.Result.Code == 0 {
188+
resp.Result.Code = http.StatusOK
189+
}
190+
}
191+
172192
// GetName returns the name of the webhook.
173193
func (w *Webhook) GetName() string {
174194
w.once.Do(w.setDefaults)

pkg/webhook/admission/webhook_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ var _ = Describe("admission webhook", func() {
8383
})
8484

8585
It("should deny the request", func() {
86-
expected := []byte(`{"response":{"uid":"","allowed":false}}
86+
expected := []byte(`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"code":200}}}
8787
`)
8888
wh.ServeHTTP(w, req)
8989
Expect(w.Body.Bytes()).To(Equal(expected))
@@ -102,7 +102,7 @@ var _ = Describe("admission webhook", func() {
102102
})
103103

104104
It("should deny the request", func() {
105-
expected := []byte(`{"response":{"uid":"","allowed":false}}
105+
expected := []byte(`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"code":200}}}
106106
`)
107107
wh.ServeHTTP(w, req)
108108
Expect(w.Body.Bytes()).To(Equal(expected))
@@ -162,7 +162,8 @@ var _ = Describe("admission webhook", func() {
162162
Handlers: []Handler{patcher1, patcher2},
163163
}
164164
expected := []byte(
165-
`{"response":{"uid":"","allowed":true,"patch":"W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2Fubm90YXRpb2` +
165+
`{"response":{"uid":"","allowed":true,"status":{"metadata":{},"code":200},` +
166+
`"patch":"W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2Fubm90YXRpb2` +
166167
`4vbmV3LWtleSIsInZhbHVlIjoibmV3LXZhbHVlIn0seyJvcCI6InJlcGxhY2UiLCJwYXRoIjoiL3NwZWMvcmVwbGljYXMiLC` +
167168
`J2YWx1ZSI6IjIifSx7Im9wIjoiYWRkIiwicGF0aCI6Ii9tZXRhZGF0YS9hbm5vdGF0aW9uL2hlbGxvIiwidmFsdWUiOiJ3b3JsZCJ9XQ==",` +
168169
`"patchType":"JSONPatch"}}
@@ -213,7 +214,7 @@ var _ = Describe("admission webhook", func() {
213214
Type: types.WebhookTypeMutating,
214215
Handlers: []Handler{errPatcher},
215216
}
216-
expected := []byte(`{"response":{"uid":"","allowed":false}}
217+
expected := []byte(`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"code":200}}}
217218
`)
218219
It("should deny the request", func() {
219220
wh.ServeHTTP(w, req)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
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 metrics
18+
19+
import (
20+
"github.com/prometheus/client_golang/prometheus"
21+
22+
"sigs.k8s.io/controller-runtime/pkg/metrics"
23+
)
24+
25+
var (
26+
// TotalRequests is a prometheus metric which counts the total number of requests that
27+
// the webhook server has received.
28+
TotalRequests = prometheus.NewCounterVec(
29+
prometheus.CounterOpts{
30+
Name: "controller_runtime_webhook_requests_total",
31+
Help: "Total number of admission requests",
32+
},
33+
[]string{"webhook", "succeeded"},
34+
)
35+
36+
// Duration is a prometheus metric which is a histogram of the latency
37+
// of processing an admission request.
38+
Duration = prometheus.NewHistogramVec(
39+
prometheus.HistogramOpts{
40+
Name: "controller_runtime_webhook_latency_seconds",
41+
Help: "Histogram of the latency of processing an admission request",
42+
},
43+
[]string{"webhook"},
44+
)
45+
)
46+
47+
func init() {
48+
metrics.Registry.MustRegister(
49+
TotalRequests,
50+
Duration)
51+
}

0 commit comments

Comments
 (0)