admission.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package podtolerationrestriction
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "io"
  19. "k8s.io/klog"
  20. corev1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/errors"
  22. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  23. "k8s.io/apiserver/pkg/admission"
  24. genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
  25. "k8s.io/client-go/informers"
  26. "k8s.io/client-go/kubernetes"
  27. corev1listers "k8s.io/client-go/listers/core/v1"
  28. api "k8s.io/kubernetes/pkg/apis/core"
  29. qoshelper "k8s.io/kubernetes/pkg/apis/core/helper/qos"
  30. k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
  31. "k8s.io/kubernetes/pkg/util/tolerations"
  32. pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
  33. )
  34. // PluginName is a string with the name of the plugin
  35. const PluginName = "PodTolerationRestriction"
  36. // Register registers a plugin
  37. func Register(plugins *admission.Plugins) {
  38. plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
  39. pluginConfig, err := loadConfiguration(config)
  40. if err != nil {
  41. return nil, err
  42. }
  43. return NewPodTolerationsPlugin(pluginConfig), nil
  44. })
  45. }
  46. // The annotation keys for default and whitelist of tolerations
  47. const (
  48. NSDefaultTolerations string = "scheduler.alpha.kubernetes.io/defaultTolerations"
  49. NSWLTolerations string = "scheduler.alpha.kubernetes.io/tolerationsWhitelist"
  50. )
  51. var _ admission.MutationInterface = &Plugin{}
  52. var _ admission.ValidationInterface = &Plugin{}
  53. var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
  54. var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
  55. // Plugin contains the client used by the admission controller
  56. type Plugin struct {
  57. *admission.Handler
  58. client kubernetes.Interface
  59. namespaceLister corev1listers.NamespaceLister
  60. pluginConfig *pluginapi.Configuration
  61. }
  62. // Admit checks the admission policy and triggers corresponding actions
  63. func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
  64. if shouldIgnore(a) {
  65. return nil
  66. }
  67. if !p.WaitForReady() {
  68. return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
  69. }
  70. pod := a.GetObject().(*api.Pod)
  71. var extraTolerations []api.Toleration
  72. if a.GetOperation() == admission.Create {
  73. ts, err := p.getNamespaceDefaultTolerations(a.GetNamespace())
  74. if err != nil {
  75. return err
  76. }
  77. // If the namespace has not specified its default tolerations,
  78. // fall back to cluster's default tolerations.
  79. if ts == nil {
  80. ts = p.pluginConfig.Default
  81. }
  82. extraTolerations = ts
  83. }
  84. if qoshelper.GetPodQOS(pod) != api.PodQOSBestEffort {
  85. extraTolerations = append(extraTolerations, api.Toleration{
  86. Key: corev1.TaintNodeMemoryPressure,
  87. Operator: api.TolerationOpExists,
  88. Effect: api.TaintEffectNoSchedule,
  89. })
  90. }
  91. // Final merge of tolerations irrespective of pod type.
  92. if len(extraTolerations) > 0 {
  93. pod.Spec.Tolerations = tolerations.MergeTolerations(pod.Spec.Tolerations, extraTolerations)
  94. }
  95. return p.Validate(ctx, a, o)
  96. }
  97. // Validate we can obtain a whitelist of tolerations
  98. func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
  99. if shouldIgnore(a) {
  100. return nil
  101. }
  102. if !p.WaitForReady() {
  103. return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
  104. }
  105. // whitelist verification.
  106. pod := a.GetObject().(*api.Pod)
  107. if len(pod.Spec.Tolerations) > 0 {
  108. whitelist, err := p.getNamespaceTolerationsWhitelist(a.GetNamespace())
  109. if err != nil {
  110. return err
  111. }
  112. // If the namespace has not specified its tolerations whitelist,
  113. // fall back to cluster's whitelist of tolerations.
  114. if whitelist == nil {
  115. whitelist = p.pluginConfig.Whitelist
  116. }
  117. if len(whitelist) > 0 {
  118. // check if the merged pod tolerations satisfy its namespace whitelist
  119. if !tolerations.VerifyAgainstWhitelist(pod.Spec.Tolerations, whitelist) {
  120. return fmt.Errorf("pod tolerations (possibly merged with namespace default tolerations) conflict with its namespace whitelist")
  121. }
  122. }
  123. }
  124. return nil
  125. }
  126. func shouldIgnore(a admission.Attributes) bool {
  127. resource := a.GetResource().GroupResource()
  128. if resource != api.Resource("pods") {
  129. return true
  130. }
  131. if a.GetSubresource() != "" {
  132. // only run the checks below on pods proper and not subresources
  133. return true
  134. }
  135. obj := a.GetObject()
  136. _, ok := obj.(*api.Pod)
  137. if !ok {
  138. klog.Errorf("expected pod but got %s", a.GetKind().Kind)
  139. return true
  140. }
  141. return false
  142. }
  143. // NewPodTolerationsPlugin initializes a Plugin
  144. func NewPodTolerationsPlugin(pluginConfig *pluginapi.Configuration) *Plugin {
  145. return &Plugin{
  146. Handler: admission.NewHandler(admission.Create, admission.Update),
  147. pluginConfig: pluginConfig,
  148. }
  149. }
  150. // SetExternalKubeClientSet sets th client
  151. func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
  152. p.client = client
  153. }
  154. // SetExternalKubeInformerFactory initializes the Informer Factory
  155. func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  156. namespaceInformer := f.Core().V1().Namespaces()
  157. p.namespaceLister = namespaceInformer.Lister()
  158. p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
  159. }
  160. // ValidateInitialization checks the object is properly initialized
  161. func (p *Plugin) ValidateInitialization() error {
  162. if p.namespaceLister == nil {
  163. return fmt.Errorf("missing namespaceLister")
  164. }
  165. if p.client == nil {
  166. return fmt.Errorf("missing client")
  167. }
  168. return nil
  169. }
  170. // in exceptional cases, this can result in two live calls, but once the cache catches up, that will stop.
  171. func (p *Plugin) getNamespace(nsName string) (*corev1.Namespace, error) {
  172. namespace, err := p.namespaceLister.Get(nsName)
  173. if errors.IsNotFound(err) {
  174. // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
  175. namespace, err = p.client.CoreV1().Namespaces().Get(context.TODO(), nsName, metav1.GetOptions{})
  176. if err != nil {
  177. if errors.IsNotFound(err) {
  178. return nil, err
  179. }
  180. return nil, errors.NewInternalError(err)
  181. }
  182. } else if err != nil {
  183. return nil, errors.NewInternalError(err)
  184. }
  185. return namespace, nil
  186. }
  187. func (p *Plugin) getNamespaceDefaultTolerations(nsName string) ([]api.Toleration, error) {
  188. ns, err := p.getNamespace(nsName)
  189. if err != nil {
  190. return nil, err
  191. }
  192. return extractNSTolerations(ns, NSDefaultTolerations)
  193. }
  194. func (p *Plugin) getNamespaceTolerationsWhitelist(nsName string) ([]api.Toleration, error) {
  195. ns, err := p.getNamespace(nsName)
  196. if err != nil {
  197. return nil, err
  198. }
  199. return extractNSTolerations(ns, NSWLTolerations)
  200. }
  201. // extractNSTolerations extracts default or whitelist of tolerations from
  202. // following namespace annotations keys: "scheduler.alpha.kubernetes.io/defaultTolerations"
  203. // and "scheduler.alpha.kubernetes.io/tolerationsWhitelist". If these keys are
  204. // unset (nil), extractNSTolerations returns nil. If the value to these
  205. // keys are set to empty, an empty toleration is returned, otherwise
  206. // configured tolerations are returned.
  207. func extractNSTolerations(ns *corev1.Namespace, key string) ([]api.Toleration, error) {
  208. // if a namespace does not have any annotations
  209. if len(ns.Annotations) == 0 {
  210. return nil, nil
  211. }
  212. // if NSWLTolerations or NSDefaultTolerations does not exist
  213. if _, ok := ns.Annotations[key]; !ok {
  214. return nil, nil
  215. }
  216. // if value is set to empty
  217. if len(ns.Annotations[key]) == 0 {
  218. return []api.Toleration{}, nil
  219. }
  220. var v1Tolerations []corev1.Toleration
  221. err := json.Unmarshal([]byte(ns.Annotations[key]), &v1Tolerations)
  222. if err != nil {
  223. return nil, err
  224. }
  225. ts := make([]api.Toleration, len(v1Tolerations))
  226. for i := range v1Tolerations {
  227. if err := k8s_api_v1.Convert_v1_Toleration_To_core_Toleration(&v1Tolerations[i], &ts[i], nil); err != nil {
  228. return nil, err
  229. }
  230. }
  231. return ts, nil
  232. }