admission.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. "encoding/json"
  16. "fmt"
  17. "io"
  18. "k8s.io/klog"
  19. corev1 "k8s.io/api/core/v1"
  20. "k8s.io/apimachinery/pkg/api/errors"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apiserver/pkg/admission"
  23. genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
  24. "k8s.io/client-go/informers"
  25. "k8s.io/client-go/kubernetes"
  26. corev1listers "k8s.io/client-go/listers/core/v1"
  27. api "k8s.io/kubernetes/pkg/apis/core"
  28. qoshelper "k8s.io/kubernetes/pkg/apis/core/helper/qos"
  29. k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
  30. schedulerapi "k8s.io/kubernetes/pkg/scheduler/api"
  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(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 finalTolerations []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. if len(ts) > 0 {
  83. if len(pod.Spec.Tolerations) > 0 {
  84. if tolerations.IsConflict(ts, pod.Spec.Tolerations) {
  85. return fmt.Errorf("namespace tolerations and pod tolerations conflict")
  86. }
  87. // modified pod tolerations = namespace tolerations + current pod tolerations
  88. finalTolerations = tolerations.MergeTolerations(ts, pod.Spec.Tolerations)
  89. } else {
  90. finalTolerations = ts
  91. }
  92. } else {
  93. finalTolerations = pod.Spec.Tolerations
  94. }
  95. } else {
  96. finalTolerations = pod.Spec.Tolerations
  97. }
  98. if qoshelper.GetPodQOS(pod) != api.PodQOSBestEffort {
  99. finalTolerations = tolerations.MergeTolerations(finalTolerations, []api.Toleration{
  100. {
  101. Key: schedulerapi.TaintNodeMemoryPressure,
  102. Operator: api.TolerationOpExists,
  103. Effect: api.TaintEffectNoSchedule,
  104. },
  105. })
  106. }
  107. // Final merge of tolerations irrespective of pod type, if the user while creating pods gives
  108. // conflicting tolerations(with same key+effect), the existing ones should be overwritten by latest one
  109. pod.Spec.Tolerations = tolerations.MergeTolerations(finalTolerations, []api.Toleration{})
  110. return p.Validate(a, o)
  111. }
  112. // Validate we can obtain a whitelist of tolerations
  113. func (p *Plugin) Validate(a admission.Attributes, o admission.ObjectInterfaces) error {
  114. if shouldIgnore(a) {
  115. return nil
  116. }
  117. if !p.WaitForReady() {
  118. return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
  119. }
  120. // whitelist verification.
  121. pod := a.GetObject().(*api.Pod)
  122. if len(pod.Spec.Tolerations) > 0 {
  123. whitelist, err := p.getNamespaceTolerationsWhitelist(a.GetNamespace())
  124. if err != nil {
  125. return err
  126. }
  127. // If the namespace has not specified its tolerations whitelist,
  128. // fall back to cluster's whitelist of tolerations.
  129. if whitelist == nil {
  130. whitelist = p.pluginConfig.Whitelist
  131. }
  132. if len(whitelist) > 0 {
  133. // check if the merged pod tolerations satisfy its namespace whitelist
  134. if !tolerations.VerifyAgainstWhitelist(pod.Spec.Tolerations, whitelist) {
  135. return fmt.Errorf("pod tolerations (possibly merged with namespace default tolerations) conflict with its namespace whitelist")
  136. }
  137. }
  138. }
  139. return nil
  140. }
  141. func shouldIgnore(a admission.Attributes) bool {
  142. resource := a.GetResource().GroupResource()
  143. if resource != api.Resource("pods") {
  144. return true
  145. }
  146. if a.GetSubresource() != "" {
  147. // only run the checks below on pods proper and not subresources
  148. return true
  149. }
  150. obj := a.GetObject()
  151. _, ok := obj.(*api.Pod)
  152. if !ok {
  153. klog.Errorf("expected pod but got %s", a.GetKind().Kind)
  154. return true
  155. }
  156. return false
  157. }
  158. // NewPodTolerationsPlugin initializes a Plugin
  159. func NewPodTolerationsPlugin(pluginConfig *pluginapi.Configuration) *Plugin {
  160. return &Plugin{
  161. Handler: admission.NewHandler(admission.Create, admission.Update),
  162. pluginConfig: pluginConfig,
  163. }
  164. }
  165. // SetExternalKubeClientSet sets th client
  166. func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
  167. p.client = client
  168. }
  169. // SetExternalKubeInformerFactory initializes the Informer Factory
  170. func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  171. namespaceInformer := f.Core().V1().Namespaces()
  172. p.namespaceLister = namespaceInformer.Lister()
  173. p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
  174. }
  175. // ValidateInitialization checks the object is properly initialized
  176. func (p *Plugin) ValidateInitialization() error {
  177. if p.namespaceLister == nil {
  178. return fmt.Errorf("missing namespaceLister")
  179. }
  180. if p.client == nil {
  181. return fmt.Errorf("missing client")
  182. }
  183. return nil
  184. }
  185. // in exceptional cases, this can result in two live calls, but once the cache catches up, that will stop.
  186. func (p *Plugin) getNamespace(nsName string) (*corev1.Namespace, error) {
  187. namespace, err := p.namespaceLister.Get(nsName)
  188. if errors.IsNotFound(err) {
  189. // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not
  190. namespace, err = p.client.CoreV1().Namespaces().Get(nsName, metav1.GetOptions{})
  191. if err != nil {
  192. if errors.IsNotFound(err) {
  193. return nil, err
  194. }
  195. return nil, errors.NewInternalError(err)
  196. }
  197. } else if err != nil {
  198. return nil, errors.NewInternalError(err)
  199. }
  200. return namespace, nil
  201. }
  202. func (p *Plugin) getNamespaceDefaultTolerations(nsName string) ([]api.Toleration, error) {
  203. ns, err := p.getNamespace(nsName)
  204. if err != nil {
  205. return nil, err
  206. }
  207. return extractNSTolerations(ns, NSDefaultTolerations)
  208. }
  209. func (p *Plugin) getNamespaceTolerationsWhitelist(nsName string) ([]api.Toleration, error) {
  210. ns, err := p.getNamespace(nsName)
  211. if err != nil {
  212. return nil, err
  213. }
  214. return extractNSTolerations(ns, NSWLTolerations)
  215. }
  216. // extractNSTolerations extracts default or whitelist of tolerations from
  217. // following namespace annotations keys: "scheduler.alpha.kubernetes.io/defaultTolerations"
  218. // and "scheduler.alpha.kubernetes.io/tolerationsWhitelist". If these keys are
  219. // unset (nil), extractNSTolerations returns nil. If the value to these
  220. // keys are set to empty, an empty toleration is returned, otherwise
  221. // configured tolerations are returned.
  222. func extractNSTolerations(ns *corev1.Namespace, key string) ([]api.Toleration, error) {
  223. // if a namespace does not have any annotations
  224. if len(ns.Annotations) == 0 {
  225. return nil, nil
  226. }
  227. // if NSWLTolerations or NSDefaultTolerations does not exist
  228. if _, ok := ns.Annotations[key]; !ok {
  229. return nil, nil
  230. }
  231. // if value is set to empty
  232. if len(ns.Annotations[key]) == 0 {
  233. return []api.Toleration{}, nil
  234. }
  235. var v1Tolerations []corev1.Toleration
  236. err := json.Unmarshal([]byte(ns.Annotations[key]), &v1Tolerations)
  237. if err != nil {
  238. return nil, err
  239. }
  240. ts := make([]api.Toleration, len(v1Tolerations))
  241. for i := range v1Tolerations {
  242. if err := k8s_api_v1.Convert_v1_Toleration_To_core_Toleration(&v1Tolerations[i], &ts[i], nil); err != nil {
  243. return nil, err
  244. }
  245. }
  246. return ts, nil
  247. }