admission.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. Copyright 2016 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 podnodeselector
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "reflect"
  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/apimachinery/pkg/labels"
  24. "k8s.io/apimachinery/pkg/util/yaml"
  25. "k8s.io/apiserver/pkg/admission"
  26. genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
  27. "k8s.io/client-go/informers"
  28. "k8s.io/client-go/kubernetes"
  29. corev1listers "k8s.io/client-go/listers/core/v1"
  30. api "k8s.io/kubernetes/pkg/apis/core"
  31. )
  32. // NamespaceNodeSelectors is for assigning node selectors labels to
  33. // namespaces. Default value is the annotation key
  34. // scheduler.alpha.kubernetes.io/node-selector
  35. var NamespaceNodeSelectors = []string{"scheduler.alpha.kubernetes.io/node-selector"}
  36. // PluginName is a string with the name of the plugin
  37. const PluginName = "PodNodeSelector"
  38. // Register registers a plugin
  39. func Register(plugins *admission.Plugins) {
  40. plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
  41. // TODO move this to a versioned configuration file format.
  42. pluginConfig := readConfig(config)
  43. plugin := NewPodNodeSelector(pluginConfig.PodNodeSelectorPluginConfig)
  44. return plugin, nil
  45. })
  46. }
  47. // Plugin is an implementation of admission.Interface.
  48. type Plugin struct {
  49. *admission.Handler
  50. client kubernetes.Interface
  51. namespaceLister corev1listers.NamespaceLister
  52. // global default node selector and namespace whitelists in a cluster.
  53. clusterNodeSelectors map[string]string
  54. }
  55. var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
  56. var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
  57. type pluginConfig struct {
  58. PodNodeSelectorPluginConfig map[string]string
  59. }
  60. // readConfig reads default value of clusterDefaultNodeSelector
  61. // from the file provided with --admission-control-config-file
  62. // If the file is not supplied, it defaults to ""
  63. // The format in a file:
  64. // podNodeSelectorPluginConfig:
  65. // clusterDefaultNodeSelector: <node-selectors-labels>
  66. // namespace1: <node-selectors-labels>
  67. // namespace2: <node-selectors-labels>
  68. func readConfig(config io.Reader) *pluginConfig {
  69. defaultConfig := &pluginConfig{}
  70. if config == nil || reflect.ValueOf(config).IsNil() {
  71. return defaultConfig
  72. }
  73. d := yaml.NewYAMLOrJSONDecoder(config, 4096)
  74. for {
  75. if err := d.Decode(defaultConfig); err != nil {
  76. if err != io.EOF {
  77. continue
  78. }
  79. }
  80. break
  81. }
  82. return defaultConfig
  83. }
  84. // Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster.
  85. func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
  86. if shouldIgnore(a) {
  87. return nil
  88. }
  89. if !p.WaitForReady() {
  90. return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
  91. }
  92. resource := a.GetResource().GroupResource()
  93. pod := a.GetObject().(*api.Pod)
  94. namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
  95. if err != nil {
  96. return err
  97. }
  98. if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
  99. return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
  100. }
  101. // Merge pod node selector = namespace node selector + current pod node selector
  102. // second selector wins
  103. podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector)
  104. pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels)
  105. return p.Validate(ctx, a, o)
  106. }
  107. // Validate ensures that the pod node selector is allowed
  108. func (p *Plugin) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
  109. if shouldIgnore(a) {
  110. return nil
  111. }
  112. if !p.WaitForReady() {
  113. return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
  114. }
  115. resource := a.GetResource().GroupResource()
  116. pod := a.GetObject().(*api.Pod)
  117. namespaceNodeSelector, err := p.getNamespaceNodeSelectorMap(a.GetNamespace())
  118. if err != nil {
  119. return err
  120. }
  121. if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) {
  122. return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector"))
  123. }
  124. // whitelist verification
  125. whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[a.GetNamespace()])
  126. if err != nil {
  127. return err
  128. }
  129. if !labels.AreLabelsInWhiteList(pod.Spec.NodeSelector, whitelist) {
  130. return errors.NewForbidden(resource, pod.Name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist"))
  131. }
  132. return nil
  133. }
  134. func (p *Plugin) getNamespaceNodeSelectorMap(namespaceName string) (labels.Set, error) {
  135. namespace, err := p.namespaceLister.Get(namespaceName)
  136. if errors.IsNotFound(err) {
  137. namespace, err = p.defaultGetNamespace(namespaceName)
  138. if err != nil {
  139. if errors.IsNotFound(err) {
  140. return nil, err
  141. }
  142. return nil, errors.NewInternalError(err)
  143. }
  144. } else if err != nil {
  145. return nil, errors.NewInternalError(err)
  146. }
  147. return p.getNodeSelectorMap(namespace)
  148. }
  149. func shouldIgnore(a admission.Attributes) bool {
  150. resource := a.GetResource().GroupResource()
  151. if resource != api.Resource("pods") {
  152. return true
  153. }
  154. if a.GetSubresource() != "" {
  155. // only run the checks below on pods proper and not subresources
  156. return true
  157. }
  158. _, ok := a.GetObject().(*api.Pod)
  159. if !ok {
  160. klog.Errorf("expected pod but got %s", a.GetKind().Kind)
  161. return true
  162. }
  163. return false
  164. }
  165. // NewPodNodeSelector initializes a podNodeSelector
  166. func NewPodNodeSelector(clusterNodeSelectors map[string]string) *Plugin {
  167. return &Plugin{
  168. Handler: admission.NewHandler(admission.Create),
  169. clusterNodeSelectors: clusterNodeSelectors,
  170. }
  171. }
  172. // SetExternalKubeClientSet sets the plugin's client
  173. func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
  174. p.client = client
  175. }
  176. // SetExternalKubeInformerFactory configures the plugin's informer factory
  177. func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  178. namespaceInformer := f.Core().V1().Namespaces()
  179. p.namespaceLister = namespaceInformer.Lister()
  180. p.SetReadyFunc(namespaceInformer.Informer().HasSynced)
  181. }
  182. // ValidateInitialization verifies the object has been properly initialized
  183. func (p *Plugin) ValidateInitialization() error {
  184. if p.namespaceLister == nil {
  185. return fmt.Errorf("missing namespaceLister")
  186. }
  187. if p.client == nil {
  188. return fmt.Errorf("missing client")
  189. }
  190. return nil
  191. }
  192. func (p *Plugin) defaultGetNamespace(name string) (*corev1.Namespace, error) {
  193. namespace, err := p.client.CoreV1().Namespaces().Get(context.TODO(), name, metav1.GetOptions{})
  194. if err != nil {
  195. return nil, fmt.Errorf("namespace %s does not exist", name)
  196. }
  197. return namespace, nil
  198. }
  199. func (p *Plugin) getNodeSelectorMap(namespace *corev1.Namespace) (labels.Set, error) {
  200. selector := labels.Set{}
  201. var err error
  202. found := false
  203. if len(namespace.ObjectMeta.Annotations) > 0 {
  204. for _, annotation := range NamespaceNodeSelectors {
  205. if ns, ok := namespace.ObjectMeta.Annotations[annotation]; ok {
  206. labelsMap, err := labels.ConvertSelectorToLabelsMap(ns)
  207. if err != nil {
  208. return labels.Set{}, err
  209. }
  210. if labels.Conflicts(selector, labelsMap) {
  211. nsName := namespace.ObjectMeta.Name
  212. return labels.Set{}, fmt.Errorf("%s annotations' node label selectors conflict", nsName)
  213. }
  214. selector = labels.Merge(selector, labelsMap)
  215. found = true
  216. }
  217. }
  218. }
  219. if !found {
  220. selector, err = labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors["clusterDefaultNodeSelector"])
  221. if err != nil {
  222. return labels.Set{}, err
  223. }
  224. }
  225. return selector, nil
  226. }