...

Source file src/k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/admission.go

Documentation: k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction

     1  /*
     2  Copyright 2017 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 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  // PluginName is a string with the name of the plugin
    43  const PluginName = "PodTolerationRestriction"
    44  
    45  // Register registers a plugin
    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  // The annotation keys for default and whitelist of tolerations
    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  // Plugin contains the client used by the admission controller
    68  type Plugin struct {
    69  	*admission.Handler
    70  	client          kubernetes.Interface
    71  	namespaceLister corev1listers.NamespaceLister
    72  	pluginConfig    *pluginapi.Configuration
    73  }
    74  
    75  // Admit checks the admission policy and triggers corresponding actions
    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  		// If the namespace has not specified its default tolerations,
    94  		// fall back to cluster's default tolerations.
    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  	// Final merge of tolerations irrespective of pod type.
   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  // Validate we can obtain a whitelist of tolerations
   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  	// whitelist verification.
   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  		// If the namespace has not specified its tolerations whitelist,
   136  		// fall back to cluster's whitelist of tolerations.
   137  		if whitelist == nil {
   138  			whitelist = p.pluginConfig.Whitelist
   139  			whitelistScope = "cluster"
   140  		}
   141  
   142  		if len(whitelist) > 0 {
   143  			// check if the merged pod tolerations satisfy its namespace whitelist
   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  		// only run the checks below on pods proper and not subresources
   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  // NewPodTolerationsPlugin initializes a Plugin
   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  // SetExternalKubeClientSet sets th client
   182  func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
   183  	p.client = client
   184  }
   185  
   186  // SetExternalKubeInformerFactory initializes the Informer Factory
   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  // ValidateInitialization checks the object is properly initialized
   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  // in exceptional cases, this can result in two live calls, but once the cache catches up, that will stop.
   206  func (p *Plugin) getNamespace(nsName string) (*corev1.Namespace, error) {
   207  	namespace, err := p.namespaceLister.Get(nsName)
   208  	if errors.IsNotFound(err) {
   209  		// in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
   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  // extractNSTolerations extracts default or whitelist of tolerations from
   241  // following namespace annotations keys: "scheduler.alpha.kubernetes.io/defaultTolerations"
   242  // and "scheduler.alpha.kubernetes.io/tolerationsWhitelist". If these keys are
   243  // unset (nil), extractNSTolerations returns nil. If the value to these
   244  // keys are set to empty, an empty toleration is returned, otherwise
   245  // configured tolerations are returned.
   246  func extractNSTolerations(ns *corev1.Namespace, key string) ([]api.Toleration, error) {
   247  	// if a namespace does not have any annotations
   248  	if len(ns.Annotations) == 0 {
   249  		return nil, nil
   250  	}
   251  
   252  	// if NSWLTolerations or NSDefaultTolerations does not exist
   253  	if _, ok := ns.Annotations[key]; !ok {
   254  		return nil, nil
   255  	}
   256  
   257  	// if value is set to empty
   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