admission.go 23 KB

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