/
webhook.go
153 lines (132 loc) · 4.28 KB
/
webhook.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
144
145
146
147
148
149
150
151
152
153
package priority
import (
"context"
"net/http"
"time"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kitlog "github.com/go-kit/kit/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission/types"
)
const (
PriorityInjectorFQDN = "priority-injector.workloads.crd.gocardless.com"
NamespaceLabel = "theatre-priority-injector"
)
func NewWebhook(logger kitlog.Logger, mgr manager.Manager, injectorOpts InjectorOptions, opts ...func(*admission.Handler)) (*admission.Webhook, error) {
var handler admission.Handler
handler = &injector{
logger: kitlog.With(logger, "component", "PriorityInjector"),
decoder: mgr.GetAdmissionDecoder(),
client: mgr.GetClient(),
opts: injectorOpts,
}
for _, opt := range opts {
opt(&handler)
}
namespaceSelectors := &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
metav1.LabelSelectorRequirement{
Key: NamespaceLabel,
Operator: metav1.LabelSelectorOpExists,
},
},
}
return builder.NewWebhookBuilder().
Name(PriorityInjectorFQDN).
Mutating().
Operations(admissionregistrationv1beta1.Create).
ForType(&corev1.Pod{}).
FailurePolicy(admissionregistrationv1beta1.Fail).
NamespaceSelector(namespaceSelectors).
Handlers(handler).
WithManager(mgr).
Build()
}
type injector struct {
logger kitlog.Logger
decoder types.Decoder
client client.Client
opts InjectorOptions
}
type InjectorOptions struct{}
var (
podLabels = []string{"pod_namespace"}
handleTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "theatre_workloads_priority_injector_handle_total",
Help: "Count of requests handled by the webhook",
},
podLabels,
)
mutateTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "theatre_workloads_priority_injector_mutate_total",
Help: "Count of pods mutated by the webhook",
},
podLabels,
)
skipTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "theatre_workloads_priority_injector_skip_total",
Help: "Count of pods skipped by the webhook",
},
podLabels,
)
errorsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "theatre_workloads_priority_injector_errors_total",
Help: "Count of not-allowed responses from webhook",
},
podLabels,
)
)
func (i *injector) Handle(ctx context.Context, req types.Request) (resp types.Response) {
labels := prometheus.Labels{"pod_namespace": req.AdmissionRequest.Namespace}
logger := kitlog.With(i.logger, "uuid", string(req.AdmissionRequest.UID))
logger.Log("event", "request.start")
defer func(start time.Time) {
logger.Log("event", "request.end", "duration", time.Since(start).Seconds())
handleTotal.With(labels).Inc()
{ // add 0 to initialise the metrics
mutateTotal.With(labels).Add(0)
skipTotal.With(labels).Add(0)
errorsTotal.With(labels).Add(0)
}
// Catch any Allowed=false responses, as this means we've failed to accept this pod
if !resp.Response.Allowed {
errorsTotal.With(labels).Inc()
}
}(time.Now())
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: req.AdmissionRequest.Namespace,
},
}
nsName, _ := client.ObjectKeyFromObject(ns)
if err := i.client.Get(ctx, nsName, ns); err != nil {
return admission.ErrorResponse(http.StatusBadRequest, err)
}
pod := &corev1.Pod{}
if err := i.decoder.Decode(req, pod); err != nil {
return admission.ErrorResponse(http.StatusBadRequest, err)
}
priorityClassName, ok := ns.ObjectMeta.Labels[NamespaceLabel]
if !ok {
logger.Log("event", "pod.skipped", "msg", "no priority label found")
skipTotal.With(labels).Inc()
return admission.PatchResponse(pod, pod)
}
mutateTotal.With(labels).Inc() // we are committed to mutating this pod now
logger.Log("event", "pod.assign_priority_class", "class", priorityClassName)
copy := pod.DeepCopy()
copy.Spec.PriorityClassName = priorityClassName
copy.Spec.Priority = nil
return admission.PatchResponse(pod, copy)
}