admission.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613
  1. /*
  2. Copyright 2014 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 limitranger
  14. import (
  15. "context"
  16. "fmt"
  17. "io"
  18. "sort"
  19. "strings"
  20. "time"
  21. "github.com/hashicorp/golang-lru"
  22. corev1 "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/api/meta"
  24. "k8s.io/apimachinery/pkg/api/resource"
  25. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  26. "k8s.io/apimachinery/pkg/labels"
  27. "k8s.io/apimachinery/pkg/runtime"
  28. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  29. "k8s.io/apiserver/pkg/admission"
  30. genericadmissioninitailizer "k8s.io/apiserver/pkg/admission/initializer"
  31. "k8s.io/client-go/informers"
  32. "k8s.io/client-go/kubernetes"
  33. corev1listers "k8s.io/client-go/listers/core/v1"
  34. api "k8s.io/kubernetes/pkg/apis/core"
  35. )
  36. const (
  37. limitRangerAnnotation = "kubernetes.io/limit-ranger"
  38. // PluginName indicates name of admission plugin.
  39. PluginName = "LimitRanger"
  40. )
  41. // Register registers a plugin
  42. func Register(plugins *admission.Plugins) {
  43. plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
  44. return NewLimitRanger(&DefaultLimitRangerActions{})
  45. })
  46. }
  47. // LimitRanger enforces usage limits on a per resource basis in the namespace
  48. type LimitRanger struct {
  49. *admission.Handler
  50. client kubernetes.Interface
  51. actions LimitRangerActions
  52. lister corev1listers.LimitRangeLister
  53. // liveLookups holds the last few live lookups we've done to help ammortize cost on repeated lookup failures.
  54. // This let's us handle the case of latent caches, by looking up actual results for a namespace on cache miss/no results.
  55. // We track the lookup result here so that for repeated requests, we don't look it up very often.
  56. liveLookupCache *lru.Cache
  57. liveTTL time.Duration
  58. }
  59. var _ admission.MutationInterface = &LimitRanger{}
  60. var _ admission.ValidationInterface = &LimitRanger{}
  61. var _ genericadmissioninitailizer.WantsExternalKubeInformerFactory = &LimitRanger{}
  62. var _ genericadmissioninitailizer.WantsExternalKubeClientSet = &LimitRanger{}
  63. type liveLookupEntry struct {
  64. expiry time.Time
  65. items []*corev1.LimitRange
  66. }
  67. // SetExternalKubeInformerFactory registers an informer factory into the LimitRanger
  68. func (l *LimitRanger) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
  69. limitRangeInformer := f.Core().V1().LimitRanges()
  70. l.SetReadyFunc(limitRangeInformer.Informer().HasSynced)
  71. l.lister = limitRangeInformer.Lister()
  72. }
  73. // SetExternalKubeClientSet registers the client into LimitRanger
  74. func (l *LimitRanger) SetExternalKubeClientSet(client kubernetes.Interface) {
  75. l.client = client
  76. }
  77. // ValidateInitialization verifies the LimitRanger object has been properly initialized
  78. func (l *LimitRanger) ValidateInitialization() error {
  79. if l.lister == nil {
  80. return fmt.Errorf("missing limitRange lister")
  81. }
  82. if l.client == nil {
  83. return fmt.Errorf("missing client")
  84. }
  85. return nil
  86. }
  87. // Admit admits resources into cluster that do not violate any defined LimitRange in the namespace
  88. func (l *LimitRanger) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
  89. return l.runLimitFunc(a, l.actions.MutateLimit)
  90. }
  91. // Validate admits resources into cluster that do not violate any defined LimitRange in the namespace
  92. func (l *LimitRanger) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
  93. return l.runLimitFunc(a, l.actions.ValidateLimit)
  94. }
  95. func (l *LimitRanger) runLimitFunc(a admission.Attributes, limitFn func(limitRange *corev1.LimitRange, kind string, obj runtime.Object) error) (err error) {
  96. if !l.actions.SupportsAttributes(a) {
  97. return nil
  98. }
  99. // ignore all objects marked for deletion
  100. oldObj := a.GetOldObject()
  101. if oldObj != nil {
  102. oldAccessor, err := meta.Accessor(oldObj)
  103. if err != nil {
  104. return admission.NewForbidden(a, err)
  105. }
  106. if oldAccessor.GetDeletionTimestamp() != nil {
  107. return nil
  108. }
  109. }
  110. items, err := l.GetLimitRanges(a)
  111. if err != nil {
  112. return err
  113. }
  114. // ensure it meets each prescribed min/max
  115. for i := range items {
  116. limitRange := items[i]
  117. if !l.actions.SupportsLimit(limitRange) {
  118. continue
  119. }
  120. err = limitFn(limitRange, a.GetResource().Resource, a.GetObject())
  121. if err != nil {
  122. return admission.NewForbidden(a, err)
  123. }
  124. }
  125. return nil
  126. }
  127. // GetLimitRanges returns a LimitRange object with the items held in
  128. // the indexer if available, or do alive lookup of the value.
  129. func (l *LimitRanger) GetLimitRanges(a admission.Attributes) ([]*corev1.LimitRange, error) {
  130. items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything())
  131. if err != nil {
  132. return nil, admission.NewForbidden(a, fmt.Errorf("unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource()))
  133. }
  134. // if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it.
  135. if len(items) == 0 {
  136. lruItemObj, ok := l.liveLookupCache.Get(a.GetNamespace())
  137. if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) {
  138. // TODO: If there are multiple operations at the same time and cache has just expired,
  139. // this may cause multiple List operations being issued at the same time.
  140. // If there is already in-flight List() for a given namespace, we should wait until
  141. // it is finished and cache is updated instead of doing the same, also to avoid
  142. // throttling - see #22422 for details.
  143. liveList, err := l.client.CoreV1().LimitRanges(a.GetNamespace()).List(context.TODO(), metav1.ListOptions{})
  144. if err != nil {
  145. return nil, admission.NewForbidden(a, err)
  146. }
  147. newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)}
  148. for i := range liveList.Items {
  149. newEntry.items = append(newEntry.items, &liveList.Items[i])
  150. }
  151. l.liveLookupCache.Add(a.GetNamespace(), newEntry)
  152. lruItemObj = newEntry
  153. }
  154. lruEntry := lruItemObj.(liveLookupEntry)
  155. for i := range lruEntry.items {
  156. items = append(items, lruEntry.items[i])
  157. }
  158. }
  159. return items, nil
  160. }
  161. // NewLimitRanger returns an object that enforces limits based on the supplied limit function
  162. func NewLimitRanger(actions LimitRangerActions) (*LimitRanger, error) {
  163. liveLookupCache, err := lru.New(10000)
  164. if err != nil {
  165. return nil, err
  166. }
  167. if actions == nil {
  168. actions = &DefaultLimitRangerActions{}
  169. }
  170. return &LimitRanger{
  171. Handler: admission.NewHandler(admission.Create, admission.Update),
  172. actions: actions,
  173. liveLookupCache: liveLookupCache,
  174. liveTTL: time.Duration(30 * time.Second),
  175. }, nil
  176. }
  177. // defaultContainerResourceRequirements returns the default requirements for a container
  178. // the requirement.Limits are taken from the LimitRange defaults (if specified)
  179. // the requirement.Requests are taken from the LimitRange default request (if specified)
  180. func defaultContainerResourceRequirements(limitRange *corev1.LimitRange) api.ResourceRequirements {
  181. requirements := api.ResourceRequirements{}
  182. requirements.Requests = api.ResourceList{}
  183. requirements.Limits = api.ResourceList{}
  184. for i := range limitRange.Spec.Limits {
  185. limit := limitRange.Spec.Limits[i]
  186. if limit.Type == corev1.LimitTypeContainer {
  187. for k, v := range limit.DefaultRequest {
  188. requirements.Requests[api.ResourceName(k)] = v.DeepCopy()
  189. }
  190. for k, v := range limit.Default {
  191. requirements.Limits[api.ResourceName(k)] = v.DeepCopy()
  192. }
  193. }
  194. }
  195. return requirements
  196. }
  197. // mergeContainerResources handles defaulting all of the resources on a container.
  198. func mergeContainerResources(container *api.Container, defaultRequirements *api.ResourceRequirements, annotationPrefix string, annotations []string) []string {
  199. setRequests := []string{}
  200. setLimits := []string{}
  201. if container.Resources.Limits == nil {
  202. container.Resources.Limits = api.ResourceList{}
  203. }
  204. if container.Resources.Requests == nil {
  205. container.Resources.Requests = api.ResourceList{}
  206. }
  207. for k, v := range defaultRequirements.Limits {
  208. _, found := container.Resources.Limits[k]
  209. if !found {
  210. container.Resources.Limits[k] = v.DeepCopy()
  211. setLimits = append(setLimits, string(k))
  212. }
  213. }
  214. for k, v := range defaultRequirements.Requests {
  215. _, found := container.Resources.Requests[k]
  216. if !found {
  217. container.Resources.Requests[k] = v.DeepCopy()
  218. setRequests = append(setRequests, string(k))
  219. }
  220. }
  221. if len(setRequests) > 0 {
  222. sort.Strings(setRequests)
  223. a := strings.Join(setRequests, ", ") + fmt.Sprintf(" request for %s %s", annotationPrefix, container.Name)
  224. annotations = append(annotations, a)
  225. }
  226. if len(setLimits) > 0 {
  227. sort.Strings(setLimits)
  228. a := strings.Join(setLimits, ", ") + fmt.Sprintf(" limit for %s %s", annotationPrefix, container.Name)
  229. annotations = append(annotations, a)
  230. }
  231. return annotations
  232. }
  233. // mergePodResourceRequirements merges enumerated requirements with default requirements
  234. // it annotates the pod with information about what requirements were modified
  235. func mergePodResourceRequirements(pod *api.Pod, defaultRequirements *api.ResourceRequirements) {
  236. annotations := []string{}
  237. for i := range pod.Spec.Containers {
  238. annotations = mergeContainerResources(&pod.Spec.Containers[i], defaultRequirements, "container", annotations)
  239. }
  240. for i := range pod.Spec.InitContainers {
  241. annotations = mergeContainerResources(&pod.Spec.InitContainers[i], defaultRequirements, "init container", annotations)
  242. }
  243. if len(annotations) > 0 {
  244. if pod.ObjectMeta.Annotations == nil {
  245. pod.ObjectMeta.Annotations = make(map[string]string)
  246. }
  247. val := "LimitRanger plugin set: " + strings.Join(annotations, "; ")
  248. pod.ObjectMeta.Annotations[limitRangerAnnotation] = val
  249. }
  250. }
  251. // requestLimitEnforcedValues returns the specified values at a common precision to support comparability
  252. func requestLimitEnforcedValues(requestQuantity, limitQuantity, enforcedQuantity resource.Quantity) (request, limit, enforced int64) {
  253. request = requestQuantity.Value()
  254. limit = limitQuantity.Value()
  255. enforced = enforcedQuantity.Value()
  256. // do a more precise comparison if possible (if the value won't overflow)
  257. if request <= resource.MaxMilliValue && limit <= resource.MaxMilliValue && enforced <= resource.MaxMilliValue {
  258. request = requestQuantity.MilliValue()
  259. limit = limitQuantity.MilliValue()
  260. enforced = enforcedQuantity.MilliValue()
  261. }
  262. return
  263. }
  264. // minConstraint enforces the min constraint over the specified resource
  265. func minConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
  266. req, reqExists := request[api.ResourceName(resourceName)]
  267. lim, limExists := limit[api.ResourceName(resourceName)]
  268. observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
  269. if !reqExists {
  270. return fmt.Errorf("minimum %s usage per %s is %s. No request is specified", resourceName, limitType, enforced.String())
  271. }
  272. if observedReqValue < enforcedValue {
  273. return fmt.Errorf("minimum %s usage per %s is %s, but request is %s", resourceName, limitType, enforced.String(), req.String())
  274. }
  275. if limExists && (observedLimValue < enforcedValue) {
  276. return fmt.Errorf("minimum %s usage per %s is %s, but limit is %s", resourceName, limitType, enforced.String(), lim.String())
  277. }
  278. return nil
  279. }
  280. // maxRequestConstraint enforces the max constraint over the specified resource
  281. // use when specify LimitType resource doesn't recognize limit values
  282. func maxRequestConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList) error {
  283. req, reqExists := request[api.ResourceName(resourceName)]
  284. observedReqValue, _, enforcedValue := requestLimitEnforcedValues(req, resource.Quantity{}, enforced)
  285. if !reqExists {
  286. return fmt.Errorf("maximum %s usage per %s is %s. No request is specified", resourceName, limitType, enforced.String())
  287. }
  288. if observedReqValue > enforcedValue {
  289. return fmt.Errorf("maximum %s usage per %s is %s, but request is %s", resourceName, limitType, enforced.String(), req.String())
  290. }
  291. return nil
  292. }
  293. // maxConstraint enforces the max constraint over the specified resource
  294. func maxConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
  295. req, reqExists := request[api.ResourceName(resourceName)]
  296. lim, limExists := limit[api.ResourceName(resourceName)]
  297. observedReqValue, observedLimValue, enforcedValue := requestLimitEnforcedValues(req, lim, enforced)
  298. if !limExists {
  299. return fmt.Errorf("maximum %s usage per %s is %s. No limit is specified", resourceName, limitType, enforced.String())
  300. }
  301. if observedLimValue > enforcedValue {
  302. return fmt.Errorf("maximum %s usage per %s is %s, but limit is %s", resourceName, limitType, enforced.String(), lim.String())
  303. }
  304. if reqExists && (observedReqValue > enforcedValue) {
  305. return fmt.Errorf("maximum %s usage per %s is %s, but request is %s", resourceName, limitType, enforced.String(), req.String())
  306. }
  307. return nil
  308. }
  309. // limitRequestRatioConstraint enforces the limit to request ratio over the specified resource
  310. func limitRequestRatioConstraint(limitType string, resourceName string, enforced resource.Quantity, request api.ResourceList, limit api.ResourceList) error {
  311. req, reqExists := request[api.ResourceName(resourceName)]
  312. lim, limExists := limit[api.ResourceName(resourceName)]
  313. observedReqValue, observedLimValue, _ := requestLimitEnforcedValues(req, lim, enforced)
  314. if !reqExists || (observedReqValue == int64(0)) {
  315. return fmt.Errorf("%s max limit to request ratio per %s is %s, but no request is specified or request is 0", resourceName, limitType, enforced.String())
  316. }
  317. if !limExists || (observedLimValue == int64(0)) {
  318. return fmt.Errorf("%s max limit to request ratio per %s is %s, but no limit is specified or limit is 0", resourceName, limitType, enforced.String())
  319. }
  320. observedRatio := float64(observedLimValue) / float64(observedReqValue)
  321. displayObservedRatio := observedRatio
  322. maxLimitRequestRatio := float64(enforced.Value())
  323. if enforced.Value() <= resource.MaxMilliValue {
  324. observedRatio = observedRatio * 1000
  325. maxLimitRequestRatio = float64(enforced.MilliValue())
  326. }
  327. if observedRatio > maxLimitRequestRatio {
  328. return fmt.Errorf("%s max limit to request ratio per %s is %s, but provided ratio is %f", resourceName, limitType, enforced.String(), displayObservedRatio)
  329. }
  330. return nil
  331. }
  332. // sum takes the total of each named resource across all inputs
  333. // if a key is not in each input, then the output resource list will omit the key
  334. func sum(inputs []api.ResourceList) api.ResourceList {
  335. result := api.ResourceList{}
  336. keys := []api.ResourceName{}
  337. for i := range inputs {
  338. for k := range inputs[i] {
  339. keys = append(keys, k)
  340. }
  341. }
  342. for _, key := range keys {
  343. total, isSet := int64(0), true
  344. for i := range inputs {
  345. input := inputs[i]
  346. v, exists := input[key]
  347. if exists {
  348. if key == api.ResourceCPU {
  349. total = total + v.MilliValue()
  350. } else {
  351. total = total + v.Value()
  352. }
  353. } else {
  354. isSet = false
  355. }
  356. }
  357. if isSet {
  358. if key == api.ResourceCPU {
  359. result[key] = *(resource.NewMilliQuantity(total, resource.DecimalSI))
  360. } else {
  361. result[key] = *(resource.NewQuantity(total, resource.DecimalSI))
  362. }
  363. }
  364. }
  365. return result
  366. }
  367. // DefaultLimitRangerActions is the default implementation of LimitRangerActions.
  368. type DefaultLimitRangerActions struct{}
  369. // ensure DefaultLimitRangerActions implements the LimitRangerActions interface.
  370. var _ LimitRangerActions = &DefaultLimitRangerActions{}
  371. // MutateLimit enforces resource requirements of incoming resources
  372. // against enumerated constraints on the LimitRange. It may modify
  373. // the incoming object to apply default resource requirements if not
  374. // specified, and enumerated on the LimitRange
  375. func (d *DefaultLimitRangerActions) MutateLimit(limitRange *corev1.LimitRange, resourceName string, obj runtime.Object) error {
  376. switch resourceName {
  377. case "pods":
  378. return PodMutateLimitFunc(limitRange, obj.(*api.Pod))
  379. }
  380. return nil
  381. }
  382. // ValidateLimit verifies the resource requirements of incoming
  383. // resources against enumerated constraints on the LimitRange are
  384. // valid
  385. func (d *DefaultLimitRangerActions) ValidateLimit(limitRange *corev1.LimitRange, resourceName string, obj runtime.Object) error {
  386. switch resourceName {
  387. case "pods":
  388. return PodValidateLimitFunc(limitRange, obj.(*api.Pod))
  389. case "persistentvolumeclaims":
  390. return PersistentVolumeClaimValidateLimitFunc(limitRange, obj.(*api.PersistentVolumeClaim))
  391. }
  392. return nil
  393. }
  394. // SupportsAttributes ignores all calls that do not deal with pod resources or storage requests (PVCs).
  395. // Also ignores any call that has a subresource defined.
  396. func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool {
  397. if a.GetSubresource() != "" {
  398. return false
  399. }
  400. // Since containers and initContainers cannot currently be added, removed, or updated, it is unnecessary
  401. // to mutate and validate limitrange on pod updates. Trying to mutate containers or initContainers on a pod
  402. // update request will always fail pod validation because those fields are immutable once the object is created.
  403. if a.GetKind().GroupKind() == api.Kind("Pod") && a.GetOperation() == admission.Update {
  404. return false
  405. }
  406. return a.GetKind().GroupKind() == api.Kind("Pod") || a.GetKind().GroupKind() == api.Kind("PersistentVolumeClaim")
  407. }
  408. // SupportsLimit always returns true.
  409. func (d *DefaultLimitRangerActions) SupportsLimit(limitRange *corev1.LimitRange) bool {
  410. return true
  411. }
  412. // PersistentVolumeClaimValidateLimitFunc enforces storage limits for PVCs.
  413. // Users request storage via pvc.Spec.Resources.Requests. Min/Max is enforced by an admin with LimitRange.
  414. // Claims will not be modified with default values because storage is a required part of pvc.Spec.
  415. // All storage enforced values *only* apply to pvc.Spec.Resources.Requests.
  416. func PersistentVolumeClaimValidateLimitFunc(limitRange *corev1.LimitRange, pvc *api.PersistentVolumeClaim) error {
  417. var errs []error
  418. for i := range limitRange.Spec.Limits {
  419. limit := limitRange.Spec.Limits[i]
  420. limitType := limit.Type
  421. if limitType == corev1.LimitTypePersistentVolumeClaim {
  422. for k, v := range limit.Min {
  423. // normal usage of minConstraint. pvc.Spec.Resources.Limits is not recognized as user input
  424. if err := minConstraint(string(limitType), string(k), v, pvc.Spec.Resources.Requests, api.ResourceList{}); err != nil {
  425. errs = append(errs, err)
  426. }
  427. }
  428. for k, v := range limit.Max {
  429. // We want to enforce the max of the LimitRange against what
  430. // the user requested.
  431. if err := maxRequestConstraint(string(limitType), string(k), v, pvc.Spec.Resources.Requests); err != nil {
  432. errs = append(errs, err)
  433. }
  434. }
  435. }
  436. }
  437. return utilerrors.NewAggregate(errs)
  438. }
  439. // PodMutateLimitFunc sets resource requirements enumerated by the pod against
  440. // the specified LimitRange. The pod may be modified to apply default resource
  441. // requirements if not specified, and enumerated on the LimitRange
  442. func PodMutateLimitFunc(limitRange *corev1.LimitRange, pod *api.Pod) error {
  443. defaultResources := defaultContainerResourceRequirements(limitRange)
  444. mergePodResourceRequirements(pod, &defaultResources)
  445. return nil
  446. }
  447. // PodValidateLimitFunc enforces resource requirements enumerated by the pod against
  448. // the specified LimitRange.
  449. func PodValidateLimitFunc(limitRange *corev1.LimitRange, pod *api.Pod) error {
  450. var errs []error
  451. for i := range limitRange.Spec.Limits {
  452. limit := limitRange.Spec.Limits[i]
  453. limitType := limit.Type
  454. // enforce container limits
  455. if limitType == corev1.LimitTypeContainer {
  456. for j := range pod.Spec.Containers {
  457. container := &pod.Spec.Containers[j]
  458. for k, v := range limit.Min {
  459. if err := minConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil {
  460. errs = append(errs, err)
  461. }
  462. }
  463. for k, v := range limit.Max {
  464. if err := maxConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil {
  465. errs = append(errs, err)
  466. }
  467. }
  468. for k, v := range limit.MaxLimitRequestRatio {
  469. if err := limitRequestRatioConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil {
  470. errs = append(errs, err)
  471. }
  472. }
  473. }
  474. for j := range pod.Spec.InitContainers {
  475. container := &pod.Spec.InitContainers[j]
  476. for k, v := range limit.Min {
  477. if err := minConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil {
  478. errs = append(errs, err)
  479. }
  480. }
  481. for k, v := range limit.Max {
  482. if err := maxConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil {
  483. errs = append(errs, err)
  484. }
  485. }
  486. for k, v := range limit.MaxLimitRequestRatio {
  487. if err := limitRequestRatioConstraint(string(limitType), string(k), v, container.Resources.Requests, container.Resources.Limits); err != nil {
  488. errs = append(errs, err)
  489. }
  490. }
  491. }
  492. }
  493. // enforce pod limits on init containers
  494. if limitType == corev1.LimitTypePod {
  495. containerRequests, containerLimits := []api.ResourceList{}, []api.ResourceList{}
  496. for j := range pod.Spec.Containers {
  497. container := &pod.Spec.Containers[j]
  498. containerRequests = append(containerRequests, container.Resources.Requests)
  499. containerLimits = append(containerLimits, container.Resources.Limits)
  500. }
  501. podRequests := sum(containerRequests)
  502. podLimits := sum(containerLimits)
  503. for j := range pod.Spec.InitContainers {
  504. container := &pod.Spec.InitContainers[j]
  505. // take max(sum_containers, any_init_container)
  506. for k, v := range container.Resources.Requests {
  507. if v2, ok := podRequests[k]; ok {
  508. if v.Cmp(v2) > 0 {
  509. podRequests[k] = v
  510. }
  511. } else {
  512. podRequests[k] = v
  513. }
  514. }
  515. for k, v := range container.Resources.Limits {
  516. if v2, ok := podLimits[k]; ok {
  517. if v.Cmp(v2) > 0 {
  518. podLimits[k] = v
  519. }
  520. } else {
  521. podLimits[k] = v
  522. }
  523. }
  524. }
  525. for k, v := range limit.Min {
  526. if err := minConstraint(string(limitType), string(k), v, podRequests, podLimits); err != nil {
  527. errs = append(errs, err)
  528. }
  529. }
  530. for k, v := range limit.Max {
  531. if err := maxConstraint(string(limitType), string(k), v, podRequests, podLimits); err != nil {
  532. errs = append(errs, err)
  533. }
  534. }
  535. for k, v := range limit.MaxLimitRequestRatio {
  536. if err := limitRequestRatioConstraint(string(limitType), string(k), v, podRequests, podLimits); err != nil {
  537. errs = append(errs, err)
  538. }
  539. }
  540. }
  541. }
  542. return utilerrors.NewAggregate(errs)
  543. }