1
16
17 package podtolerationrestriction
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "io"
24
25 "k8s.io/klog/v2"
26
27 corev1 "k8s.io/api/core/v1"
28 "k8s.io/apimachinery/pkg/api/errors"
29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30 "k8s.io/apiserver/pkg/admission"
31 genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
32 "k8s.io/client-go/informers"
33 "k8s.io/client-go/kubernetes"
34 corev1listers "k8s.io/client-go/listers/core/v1"
35 api "k8s.io/kubernetes/pkg/apis/core"
36 qoshelper "k8s.io/kubernetes/pkg/apis/core/helper/qos"
37 k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
38 "k8s.io/kubernetes/pkg/util/tolerations"
39 pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
40 )
41
42
43 const PluginName = "PodTolerationRestriction"
44
45
46 func Register(plugins *admission.Plugins) {
47 plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
48 pluginConfig, err := loadConfiguration(config)
49 if err != nil {
50 return nil, err
51 }
52 return NewPodTolerationsPlugin(pluginConfig), nil
53 })
54 }
55
56
57 const (
58 NSDefaultTolerations string = "scheduler.alpha.kubernetes.io/defaultTolerations"
59 NSWLTolerations string = "scheduler.alpha.kubernetes.io/tolerationsWhitelist"
60 )
61
62 var _ admission.MutationInterface = &Plugin{}
63 var _ admission.ValidationInterface = &Plugin{}
64 var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
65 var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
66
67
68 type Plugin struct {
69 *admission.Handler
70 client kubernetes.Interface
71 namespaceLister corev1listers.NamespaceLister
72 pluginConfig *pluginapi.Configuration
73 }
74
75
76 func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
77 if shouldIgnore(a) {
78 return nil
79 }
80
81 if !p.WaitForReady() {
82 return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
83 }
84
85 pod := a.GetObject().(*api.Pod)
86 var extraTolerations []api.Toleration
87 if a.GetOperation() == admission.Create {
88 ts, err := p.getNamespaceDefaultTolerations(a.GetNamespace())
89 if err != nil {
90 return err
91 }
92
93
94
95 if ts == nil {
96 ts = p.pluginConfig.Default
97 }
98
99 extraTolerations = ts
100 }
101
102 if qoshelper.GetPodQOS(pod) != api.PodQOSBestEffort {
103 extraTolerations = append(extraTolerations, api.Toleration{
104 Key: corev1.TaintNodeMemoryPressure,
105 Operator: api.TolerationOpExists,
106 Effect: api.TaintEffectNoSchedule,
107 })
108 }
109
110 if len(extraTolerations) > 0 {
111 pod.Spec.Tolerations = tolerations.MergeTolerations(pod.Spec.Tolerations, extraTolerations)
112 }
113 return p.Validate(ctx, a, o)
114 }
115
116
117 func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
118 if shouldIgnore(a) {
119 return nil
120 }
121
122 if !p.WaitForReady() {
123 return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
124 }
125
126
127 pod := a.GetObject().(*api.Pod)
128 if len(pod.Spec.Tolerations) > 0 {
129 whitelist, err := p.getNamespaceTolerationsWhitelist(a.GetNamespace())
130 whitelistScope := "namespace"
131 if err != nil {
132 return err
133 }
134
135
136
137 if whitelist == nil {
138 whitelist = p.pluginConfig.Whitelist
139 whitelistScope = "cluster"
140 }
141
142 if len(whitelist) > 0 {
143
144 if !tolerations.VerifyAgainstWhitelist(pod.Spec.Tolerations, whitelist) {
145 return fmt.Errorf("pod tolerations (possibly merged with namespace default tolerations) conflict with its %s whitelist", whitelistScope)
146 }
147 }
148 }
149
150 return nil
151 }
152
153 func shouldIgnore(a admission.Attributes) bool {
154 resource := a.GetResource().GroupResource()
155 if resource != api.Resource("pods") {
156 return true
157 }
158 if a.GetSubresource() != "" {
159
160 return true
161 }
162
163 obj := a.GetObject()
164 _, ok := obj.(*api.Pod)
165 if !ok {
166 klog.Errorf("expected pod but got %s", a.GetKind().Kind)
167 return true
168 }
169
170 return false
171 }
172
173
174 func NewPodTolerationsPlugin(pluginConfig *pluginapi.Configuration) *Plugin {
175 return &Plugin{
176 Handler: admission.NewHandler(admission.Create, admission.Update),
177 pluginConfig: pluginConfig,
178 }
179 }
180
181
182 func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
183 p.client = client
184 }
185
186
187 func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
188 namespaceInformer := f.Core().V1().Namespaces()
189 p.namespaceLister = namespaceInformer.Lister()
190 p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
191
192 }
193
194
195 func (p *Plugin) ValidateInitialization() error {
196 if p.namespaceLister == nil {
197 return fmt.Errorf("missing namespaceLister")
198 }
199 if p.client == nil {
200 return fmt.Errorf("missing client")
201 }
202 return nil
203 }
204
205
206 func (p *Plugin) getNamespace(nsName string) (*corev1.Namespace, error) {
207 namespace, err := p.namespaceLister.Get(nsName)
208 if errors.IsNotFound(err) {
209
210 namespace, err = p.client.CoreV1().Namespaces().Get(context.TODO(), nsName, metav1.GetOptions{})
211 if err != nil {
212 if errors.IsNotFound(err) {
213 return nil, err
214 }
215 return nil, errors.NewInternalError(err)
216 }
217 } else if err != nil {
218 return nil, errors.NewInternalError(err)
219 }
220
221 return namespace, nil
222 }
223
224 func (p *Plugin) getNamespaceDefaultTolerations(nsName string) ([]api.Toleration, error) {
225 ns, err := p.getNamespace(nsName)
226 if err != nil {
227 return nil, err
228 }
229 return extractNSTolerations(ns, NSDefaultTolerations)
230 }
231
232 func (p *Plugin) getNamespaceTolerationsWhitelist(nsName string) ([]api.Toleration, error) {
233 ns, err := p.getNamespace(nsName)
234 if err != nil {
235 return nil, err
236 }
237 return extractNSTolerations(ns, NSWLTolerations)
238 }
239
240
241
242
243
244
245
246 func extractNSTolerations(ns *corev1.Namespace, key string) ([]api.Toleration, error) {
247
248 if len(ns.Annotations) == 0 {
249 return nil, nil
250 }
251
252
253 if _, ok := ns.Annotations[key]; !ok {
254 return nil, nil
255 }
256
257
258 if len(ns.Annotations[key]) == 0 {
259 return []api.Toleration{}, nil
260 }
261
262 var v1Tolerations []corev1.Toleration
263 err := json.Unmarshal([]byte(ns.Annotations[key]), &v1Tolerations)
264 if err != nil {
265 return nil, err
266 }
267
268 ts := make([]api.Toleration, len(v1Tolerations))
269 for i := range v1Tolerations {
270 if err := k8s_api_v1.Convert_v1_Toleration_To_core_Toleration(&v1Tolerations[i], &ts[i], nil); err != nil {
271 return nil, err
272 }
273 }
274
275 return ts, nil
276 }
277
View as plain text