helpers.go 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  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 eviction
  14. import (
  15. "fmt"
  16. "sort"
  17. "strconv"
  18. "strings"
  19. "time"
  20. "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/resource"
  22. utilfeature "k8s.io/apiserver/pkg/util/feature"
  23. "k8s.io/klog"
  24. "k8s.io/kubernetes/pkg/features"
  25. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  26. evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
  27. kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
  28. schedulerutils "k8s.io/kubernetes/pkg/scheduler/util"
  29. volumeutils "k8s.io/kubernetes/pkg/volume/util"
  30. )
  31. const (
  32. unsupportedEvictionSignal = "unsupported eviction signal %v"
  33. // Reason is the reason reported back in status.
  34. Reason = "Evicted"
  35. // nodeLowMessageFmt is the message for evictions due to resource pressure.
  36. nodeLowMessageFmt = "The node was low on resource: %v. "
  37. // nodeConditionMessageFmt is the message for evictions due to resource pressure.
  38. nodeConditionMessageFmt = "The node had condition: %v. "
  39. // containerMessageFmt provides additional information for containers exceeding requests
  40. containerMessageFmt = "Container %s was using %s, which exceeds its request of %s. "
  41. // containerEphemeralStorageMessageFmt provides additional information for containers which have exceeded their ES limit
  42. containerEphemeralStorageMessageFmt = "Container %s exceeded its local ephemeral storage limit %q. "
  43. // podEphemeralStorageMessageFmt provides additional information for pods which have exceeded their ES limit
  44. podEphemeralStorageMessageFmt = "Pod ephemeral local storage usage exceeds the total limit of containers %s. "
  45. // emptyDirMessageFmt provides additional information for empty-dir volumes which have exceeded their size limit
  46. emptyDirMessageFmt = "Usage of EmptyDir volume %q exceeds the limit %q. "
  47. // inodes, number. internal to this module, used to account for local disk inode consumption.
  48. resourceInodes v1.ResourceName = "inodes"
  49. // resourcePids, number. internal to this module, used to account for local pid consumption.
  50. resourcePids v1.ResourceName = "pids"
  51. // OffendingContainersKey is the key in eviction event annotations for the list of container names which exceeded their requests
  52. OffendingContainersKey = "offending_containers"
  53. // OffendingContainersUsageKey is the key in eviction event annotations for the list of usage of containers which exceeded their requests
  54. OffendingContainersUsageKey = "offending_containers_usage"
  55. // StarvedResourceKey is the key for the starved resource in eviction event annotations
  56. StarvedResourceKey = "starved_resource"
  57. )
  58. var (
  59. // signalToNodeCondition maps a signal to the node condition to report if threshold is met.
  60. signalToNodeCondition map[evictionapi.Signal]v1.NodeConditionType
  61. // signalToResource maps a Signal to its associated Resource.
  62. signalToResource map[evictionapi.Signal]v1.ResourceName
  63. )
  64. func init() {
  65. // map eviction signals to node conditions
  66. signalToNodeCondition = map[evictionapi.Signal]v1.NodeConditionType{}
  67. signalToNodeCondition[evictionapi.SignalMemoryAvailable] = v1.NodeMemoryPressure
  68. signalToNodeCondition[evictionapi.SignalAllocatableMemoryAvailable] = v1.NodeMemoryPressure
  69. signalToNodeCondition[evictionapi.SignalImageFsAvailable] = v1.NodeDiskPressure
  70. signalToNodeCondition[evictionapi.SignalNodeFsAvailable] = v1.NodeDiskPressure
  71. signalToNodeCondition[evictionapi.SignalImageFsInodesFree] = v1.NodeDiskPressure
  72. signalToNodeCondition[evictionapi.SignalNodeFsInodesFree] = v1.NodeDiskPressure
  73. signalToNodeCondition[evictionapi.SignalPIDAvailable] = v1.NodePIDPressure
  74. // map signals to resources (and vice-versa)
  75. signalToResource = map[evictionapi.Signal]v1.ResourceName{}
  76. signalToResource[evictionapi.SignalMemoryAvailable] = v1.ResourceMemory
  77. signalToResource[evictionapi.SignalAllocatableMemoryAvailable] = v1.ResourceMemory
  78. signalToResource[evictionapi.SignalImageFsAvailable] = v1.ResourceEphemeralStorage
  79. signalToResource[evictionapi.SignalImageFsInodesFree] = resourceInodes
  80. signalToResource[evictionapi.SignalNodeFsAvailable] = v1.ResourceEphemeralStorage
  81. signalToResource[evictionapi.SignalNodeFsInodesFree] = resourceInodes
  82. signalToResource[evictionapi.SignalPIDAvailable] = resourcePids
  83. }
  84. // validSignal returns true if the signal is supported.
  85. func validSignal(signal evictionapi.Signal) bool {
  86. _, found := signalToResource[signal]
  87. return found
  88. }
  89. // ParseThresholdConfig parses the flags for thresholds.
  90. func ParseThresholdConfig(allocatableConfig []string, evictionHard, evictionSoft, evictionSoftGracePeriod, evictionMinimumReclaim map[string]string) ([]evictionapi.Threshold, error) {
  91. results := []evictionapi.Threshold{}
  92. hardThresholds, err := parseThresholdStatements(evictionHard)
  93. if err != nil {
  94. return nil, err
  95. }
  96. results = append(results, hardThresholds...)
  97. softThresholds, err := parseThresholdStatements(evictionSoft)
  98. if err != nil {
  99. return nil, err
  100. }
  101. gracePeriods, err := parseGracePeriods(evictionSoftGracePeriod)
  102. if err != nil {
  103. return nil, err
  104. }
  105. minReclaims, err := parseMinimumReclaims(evictionMinimumReclaim)
  106. if err != nil {
  107. return nil, err
  108. }
  109. for i := range softThresholds {
  110. signal := softThresholds[i].Signal
  111. period, found := gracePeriods[signal]
  112. if !found {
  113. return nil, fmt.Errorf("grace period must be specified for the soft eviction threshold %v", signal)
  114. }
  115. softThresholds[i].GracePeriod = period
  116. }
  117. results = append(results, softThresholds...)
  118. for i := range results {
  119. for signal, minReclaim := range minReclaims {
  120. if results[i].Signal == signal {
  121. results[i].MinReclaim = &minReclaim
  122. break
  123. }
  124. }
  125. }
  126. for _, key := range allocatableConfig {
  127. if key == kubetypes.NodeAllocatableEnforcementKey {
  128. results = addAllocatableThresholds(results)
  129. break
  130. }
  131. }
  132. return results, nil
  133. }
  134. func addAllocatableThresholds(thresholds []evictionapi.Threshold) []evictionapi.Threshold {
  135. additionalThresholds := []evictionapi.Threshold{}
  136. for _, threshold := range thresholds {
  137. if threshold.Signal == evictionapi.SignalMemoryAvailable && isHardEvictionThreshold(threshold) {
  138. // Copy the SignalMemoryAvailable to SignalAllocatableMemoryAvailable
  139. additionalThresholds = append(additionalThresholds, evictionapi.Threshold{
  140. Signal: evictionapi.SignalAllocatableMemoryAvailable,
  141. Operator: threshold.Operator,
  142. Value: threshold.Value,
  143. MinReclaim: threshold.MinReclaim,
  144. })
  145. }
  146. }
  147. return append(thresholds, additionalThresholds...)
  148. }
  149. // parseThresholdStatements parses the input statements into a list of Threshold objects.
  150. func parseThresholdStatements(statements map[string]string) ([]evictionapi.Threshold, error) {
  151. if len(statements) == 0 {
  152. return nil, nil
  153. }
  154. results := []evictionapi.Threshold{}
  155. for signal, val := range statements {
  156. result, err := parseThresholdStatement(evictionapi.Signal(signal), val)
  157. if err != nil {
  158. return nil, err
  159. }
  160. if result != nil {
  161. results = append(results, *result)
  162. }
  163. }
  164. return results, nil
  165. }
  166. // parseThresholdStatement parses a threshold statement and returns a threshold,
  167. // or nil if the threshold should be ignored.
  168. func parseThresholdStatement(signal evictionapi.Signal, val string) (*evictionapi.Threshold, error) {
  169. if !validSignal(signal) {
  170. return nil, fmt.Errorf(unsupportedEvictionSignal, signal)
  171. }
  172. operator := evictionapi.OpForSignal[signal]
  173. if strings.HasSuffix(val, "%") {
  174. // ignore 0% and 100%
  175. if val == "0%" || val == "100%" {
  176. return nil, nil
  177. }
  178. percentage, err := parsePercentage(val)
  179. if err != nil {
  180. return nil, err
  181. }
  182. if percentage < 0 {
  183. return nil, fmt.Errorf("eviction percentage threshold %v must be >= 0%%: %s", signal, val)
  184. }
  185. if percentage > 100 {
  186. return nil, fmt.Errorf("eviction percentage threshold %v must be <= 100%%: %s", signal, val)
  187. }
  188. return &evictionapi.Threshold{
  189. Signal: signal,
  190. Operator: operator,
  191. Value: evictionapi.ThresholdValue{
  192. Percentage: percentage,
  193. },
  194. }, nil
  195. }
  196. quantity, err := resource.ParseQuantity(val)
  197. if err != nil {
  198. return nil, err
  199. }
  200. if quantity.Sign() < 0 || quantity.IsZero() {
  201. return nil, fmt.Errorf("eviction threshold %v must be positive: %s", signal, &quantity)
  202. }
  203. return &evictionapi.Threshold{
  204. Signal: signal,
  205. Operator: operator,
  206. Value: evictionapi.ThresholdValue{
  207. Quantity: &quantity,
  208. },
  209. }, nil
  210. }
  211. // parsePercentage parses a string representing a percentage value
  212. func parsePercentage(input string) (float32, error) {
  213. value, err := strconv.ParseFloat(strings.TrimRight(input, "%"), 32)
  214. if err != nil {
  215. return 0, err
  216. }
  217. return float32(value) / 100, nil
  218. }
  219. // parseGracePeriods parses the grace period statements
  220. func parseGracePeriods(statements map[string]string) (map[evictionapi.Signal]time.Duration, error) {
  221. if len(statements) == 0 {
  222. return nil, nil
  223. }
  224. results := map[evictionapi.Signal]time.Duration{}
  225. for signal, val := range statements {
  226. signal := evictionapi.Signal(signal)
  227. if !validSignal(signal) {
  228. return nil, fmt.Errorf(unsupportedEvictionSignal, signal)
  229. }
  230. gracePeriod, err := time.ParseDuration(val)
  231. if err != nil {
  232. return nil, err
  233. }
  234. if gracePeriod < 0 {
  235. return nil, fmt.Errorf("invalid eviction grace period specified: %v, must be a positive value", val)
  236. }
  237. results[signal] = gracePeriod
  238. }
  239. return results, nil
  240. }
  241. // parseMinimumReclaims parses the minimum reclaim statements
  242. func parseMinimumReclaims(statements map[string]string) (map[evictionapi.Signal]evictionapi.ThresholdValue, error) {
  243. if len(statements) == 0 {
  244. return nil, nil
  245. }
  246. results := map[evictionapi.Signal]evictionapi.ThresholdValue{}
  247. for signal, val := range statements {
  248. signal := evictionapi.Signal(signal)
  249. if !validSignal(signal) {
  250. return nil, fmt.Errorf(unsupportedEvictionSignal, signal)
  251. }
  252. if strings.HasSuffix(val, "%") {
  253. percentage, err := parsePercentage(val)
  254. if err != nil {
  255. return nil, err
  256. }
  257. if percentage <= 0 {
  258. return nil, fmt.Errorf("eviction percentage minimum reclaim %v must be positive: %s", signal, val)
  259. }
  260. results[signal] = evictionapi.ThresholdValue{
  261. Percentage: percentage,
  262. }
  263. continue
  264. }
  265. quantity, err := resource.ParseQuantity(val)
  266. if err != nil {
  267. return nil, err
  268. }
  269. if quantity.Sign() < 0 {
  270. return nil, fmt.Errorf("negative eviction minimum reclaim specified for %v", signal)
  271. }
  272. results[signal] = evictionapi.ThresholdValue{
  273. Quantity: &quantity,
  274. }
  275. }
  276. return results, nil
  277. }
  278. // diskUsage converts used bytes into a resource quantity.
  279. func diskUsage(fsStats *statsapi.FsStats) *resource.Quantity {
  280. if fsStats == nil || fsStats.UsedBytes == nil {
  281. return &resource.Quantity{Format: resource.BinarySI}
  282. }
  283. usage := int64(*fsStats.UsedBytes)
  284. return resource.NewQuantity(usage, resource.BinarySI)
  285. }
  286. // inodeUsage converts inodes consumed into a resource quantity.
  287. func inodeUsage(fsStats *statsapi.FsStats) *resource.Quantity {
  288. if fsStats == nil || fsStats.InodesUsed == nil {
  289. return &resource.Quantity{Format: resource.DecimalSI}
  290. }
  291. usage := int64(*fsStats.InodesUsed)
  292. return resource.NewQuantity(usage, resource.DecimalSI)
  293. }
  294. // memoryUsage converts working set into a resource quantity.
  295. func memoryUsage(memStats *statsapi.MemoryStats) *resource.Quantity {
  296. if memStats == nil || memStats.WorkingSetBytes == nil {
  297. return &resource.Quantity{Format: resource.BinarySI}
  298. }
  299. usage := int64(*memStats.WorkingSetBytes)
  300. return resource.NewQuantity(usage, resource.BinarySI)
  301. }
  302. // localVolumeNames returns the set of volumes for the pod that are local
  303. // TODO: sumamry API should report what volumes consume local storage rather than hard-code here.
  304. func localVolumeNames(pod *v1.Pod) []string {
  305. result := []string{}
  306. for _, volume := range pod.Spec.Volumes {
  307. if volume.HostPath != nil ||
  308. (volume.EmptyDir != nil && volume.EmptyDir.Medium != v1.StorageMediumMemory) ||
  309. volume.ConfigMap != nil ||
  310. volume.GitRepo != nil {
  311. result = append(result, volume.Name)
  312. }
  313. }
  314. return result
  315. }
  316. // containerUsage aggregates container disk usage and inode consumption for the specified stats to measure.
  317. func containerUsage(podStats statsapi.PodStats, statsToMeasure []fsStatsType) v1.ResourceList {
  318. disk := resource.Quantity{Format: resource.BinarySI}
  319. inodes := resource.Quantity{Format: resource.DecimalSI}
  320. for _, container := range podStats.Containers {
  321. if hasFsStatsType(statsToMeasure, fsStatsRoot) {
  322. disk.Add(*diskUsage(container.Rootfs))
  323. inodes.Add(*inodeUsage(container.Rootfs))
  324. }
  325. if hasFsStatsType(statsToMeasure, fsStatsLogs) {
  326. disk.Add(*diskUsage(container.Logs))
  327. inodes.Add(*inodeUsage(container.Logs))
  328. }
  329. }
  330. return v1.ResourceList{
  331. v1.ResourceEphemeralStorage: disk,
  332. resourceInodes: inodes,
  333. }
  334. }
  335. // podLocalVolumeUsage aggregates pod local volumes disk usage and inode consumption for the specified stats to measure.
  336. func podLocalVolumeUsage(volumeNames []string, podStats statsapi.PodStats) v1.ResourceList {
  337. disk := resource.Quantity{Format: resource.BinarySI}
  338. inodes := resource.Quantity{Format: resource.DecimalSI}
  339. for _, volumeName := range volumeNames {
  340. for _, volumeStats := range podStats.VolumeStats {
  341. if volumeStats.Name == volumeName {
  342. disk.Add(*diskUsage(&volumeStats.FsStats))
  343. inodes.Add(*inodeUsage(&volumeStats.FsStats))
  344. break
  345. }
  346. }
  347. }
  348. return v1.ResourceList{
  349. v1.ResourceEphemeralStorage: disk,
  350. resourceInodes: inodes,
  351. }
  352. }
  353. // podDiskUsage aggregates pod disk usage and inode consumption for the specified stats to measure.
  354. func podDiskUsage(podStats statsapi.PodStats, pod *v1.Pod, statsToMeasure []fsStatsType) (v1.ResourceList, error) {
  355. disk := resource.Quantity{Format: resource.BinarySI}
  356. inodes := resource.Quantity{Format: resource.DecimalSI}
  357. containerUsageList := containerUsage(podStats, statsToMeasure)
  358. disk.Add(containerUsageList[v1.ResourceEphemeralStorage])
  359. inodes.Add(containerUsageList[resourceInodes])
  360. if hasFsStatsType(statsToMeasure, fsStatsLocalVolumeSource) {
  361. volumeNames := localVolumeNames(pod)
  362. podLocalVolumeUsageList := podLocalVolumeUsage(volumeNames, podStats)
  363. disk.Add(podLocalVolumeUsageList[v1.ResourceEphemeralStorage])
  364. inodes.Add(podLocalVolumeUsageList[resourceInodes])
  365. }
  366. return v1.ResourceList{
  367. v1.ResourceEphemeralStorage: disk,
  368. resourceInodes: inodes,
  369. }, nil
  370. }
  371. // localEphemeralVolumeNames returns the set of ephemeral volumes for the pod that are local
  372. func localEphemeralVolumeNames(pod *v1.Pod) []string {
  373. result := []string{}
  374. for _, volume := range pod.Spec.Volumes {
  375. if volumeutils.IsLocalEphemeralVolume(volume) {
  376. result = append(result, volume.Name)
  377. }
  378. }
  379. return result
  380. }
  381. // podLocalEphemeralStorageUsage aggregates pod local ephemeral storage usage and inode consumption for the specified stats to measure.
  382. func podLocalEphemeralStorageUsage(podStats statsapi.PodStats, pod *v1.Pod, statsToMeasure []fsStatsType) (v1.ResourceList, error) {
  383. disk := resource.Quantity{Format: resource.BinarySI}
  384. inodes := resource.Quantity{Format: resource.DecimalSI}
  385. containerUsageList := containerUsage(podStats, statsToMeasure)
  386. disk.Add(containerUsageList[v1.ResourceEphemeralStorage])
  387. inodes.Add(containerUsageList[resourceInodes])
  388. if hasFsStatsType(statsToMeasure, fsStatsLocalVolumeSource) {
  389. volumeNames := localEphemeralVolumeNames(pod)
  390. podLocalVolumeUsageList := podLocalVolumeUsage(volumeNames, podStats)
  391. disk.Add(podLocalVolumeUsageList[v1.ResourceEphemeralStorage])
  392. inodes.Add(podLocalVolumeUsageList[resourceInodes])
  393. }
  394. return v1.ResourceList{
  395. v1.ResourceEphemeralStorage: disk,
  396. resourceInodes: inodes,
  397. }, nil
  398. }
  399. // formatThreshold formats a threshold for logging.
  400. func formatThreshold(threshold evictionapi.Threshold) string {
  401. return fmt.Sprintf("threshold(signal=%v, operator=%v, value=%v, gracePeriod=%v)", threshold.Signal, threshold.Operator, evictionapi.ThresholdValue(threshold.Value), threshold.GracePeriod)
  402. }
  403. // cachedStatsFunc returns a statsFunc based on the provided pod stats.
  404. func cachedStatsFunc(podStats []statsapi.PodStats) statsFunc {
  405. uid2PodStats := map[string]statsapi.PodStats{}
  406. for i := range podStats {
  407. uid2PodStats[podStats[i].PodRef.UID] = podStats[i]
  408. }
  409. return func(pod *v1.Pod) (statsapi.PodStats, bool) {
  410. stats, found := uid2PodStats[string(pod.UID)]
  411. return stats, found
  412. }
  413. }
  414. // Cmp compares p1 and p2 and returns:
  415. //
  416. // -1 if p1 < p2
  417. // 0 if p1 == p2
  418. // +1 if p1 > p2
  419. //
  420. type cmpFunc func(p1, p2 *v1.Pod) int
  421. // multiSorter implements the Sort interface, sorting changes within.
  422. type multiSorter struct {
  423. pods []*v1.Pod
  424. cmp []cmpFunc
  425. }
  426. // Sort sorts the argument slice according to the less functions passed to OrderedBy.
  427. func (ms *multiSorter) Sort(pods []*v1.Pod) {
  428. ms.pods = pods
  429. sort.Sort(ms)
  430. }
  431. // OrderedBy returns a Sorter that sorts using the cmp functions, in order.
  432. // Call its Sort method to sort the data.
  433. func orderedBy(cmp ...cmpFunc) *multiSorter {
  434. return &multiSorter{
  435. cmp: cmp,
  436. }
  437. }
  438. // Len is part of sort.Interface.
  439. func (ms *multiSorter) Len() int {
  440. return len(ms.pods)
  441. }
  442. // Swap is part of sort.Interface.
  443. func (ms *multiSorter) Swap(i, j int) {
  444. ms.pods[i], ms.pods[j] = ms.pods[j], ms.pods[i]
  445. }
  446. // Less is part of sort.Interface.
  447. func (ms *multiSorter) Less(i, j int) bool {
  448. p1, p2 := ms.pods[i], ms.pods[j]
  449. var k int
  450. for k = 0; k < len(ms.cmp)-1; k++ {
  451. cmpResult := ms.cmp[k](p1, p2)
  452. // p1 is less than p2
  453. if cmpResult < 0 {
  454. return true
  455. }
  456. // p1 is greater than p2
  457. if cmpResult > 0 {
  458. return false
  459. }
  460. // we don't know yet
  461. }
  462. // the last cmp func is the final decider
  463. return ms.cmp[k](p1, p2) < 0
  464. }
  465. // priority compares pods by Priority, if priority is enabled.
  466. func priority(p1, p2 *v1.Pod) int {
  467. if !utilfeature.DefaultFeatureGate.Enabled(features.PodPriority) {
  468. // If priority is not enabled, all pods are equal.
  469. return 0
  470. }
  471. priority1 := schedulerutils.GetPodPriority(p1)
  472. priority2 := schedulerutils.GetPodPriority(p2)
  473. if priority1 == priority2 {
  474. return 0
  475. }
  476. if priority1 > priority2 {
  477. return 1
  478. }
  479. return -1
  480. }
  481. // exceedMemoryRequests compares whether or not pods' memory usage exceeds their requests
  482. func exceedMemoryRequests(stats statsFunc) cmpFunc {
  483. return func(p1, p2 *v1.Pod) int {
  484. p1Stats, p1Found := stats(p1)
  485. p2Stats, p2Found := stats(p2)
  486. if !p1Found || !p2Found {
  487. // prioritize evicting the pod for which no stats were found
  488. return cmpBool(!p1Found, !p2Found)
  489. }
  490. p1Memory := memoryUsage(p1Stats.Memory)
  491. p2Memory := memoryUsage(p2Stats.Memory)
  492. p1ExceedsRequests := p1Memory.Cmp(podRequest(p1, v1.ResourceMemory)) == 1
  493. p2ExceedsRequests := p2Memory.Cmp(podRequest(p2, v1.ResourceMemory)) == 1
  494. // prioritize evicting the pod which exceeds its requests
  495. return cmpBool(p1ExceedsRequests, p2ExceedsRequests)
  496. }
  497. }
  498. // memory compares pods by largest consumer of memory relative to request.
  499. func memory(stats statsFunc) cmpFunc {
  500. return func(p1, p2 *v1.Pod) int {
  501. p1Stats, p1Found := stats(p1)
  502. p2Stats, p2Found := stats(p2)
  503. if !p1Found || !p2Found {
  504. // prioritize evicting the pod for which no stats were found
  505. return cmpBool(!p1Found, !p2Found)
  506. }
  507. // adjust p1, p2 usage relative to the request (if any)
  508. p1Memory := memoryUsage(p1Stats.Memory)
  509. p1Request := podRequest(p1, v1.ResourceMemory)
  510. p1Memory.Sub(p1Request)
  511. p2Memory := memoryUsage(p2Stats.Memory)
  512. p2Request := podRequest(p2, v1.ResourceMemory)
  513. p2Memory.Sub(p2Request)
  514. // prioritize evicting the pod which has the larger consumption of memory
  515. return p2Memory.Cmp(*p1Memory)
  516. }
  517. }
  518. // podRequest returns the total resource request of a pod which is the
  519. // max(max of init container requests, sum of container requests)
  520. func podRequest(pod *v1.Pod, resourceName v1.ResourceName) resource.Quantity {
  521. containerValue := resource.Quantity{Format: resource.BinarySI}
  522. if resourceName == v1.ResourceEphemeralStorage && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
  523. // if the local storage capacity isolation feature gate is disabled, pods request 0 disk
  524. return containerValue
  525. }
  526. for i := range pod.Spec.Containers {
  527. switch resourceName {
  528. case v1.ResourceMemory:
  529. containerValue.Add(*pod.Spec.Containers[i].Resources.Requests.Memory())
  530. case v1.ResourceEphemeralStorage:
  531. containerValue.Add(*pod.Spec.Containers[i].Resources.Requests.StorageEphemeral())
  532. }
  533. }
  534. initValue := resource.Quantity{Format: resource.BinarySI}
  535. for i := range pod.Spec.InitContainers {
  536. switch resourceName {
  537. case v1.ResourceMemory:
  538. if initValue.Cmp(*pod.Spec.InitContainers[i].Resources.Requests.Memory()) < 0 {
  539. initValue = *pod.Spec.InitContainers[i].Resources.Requests.Memory()
  540. }
  541. case v1.ResourceEphemeralStorage:
  542. if initValue.Cmp(*pod.Spec.InitContainers[i].Resources.Requests.StorageEphemeral()) < 0 {
  543. initValue = *pod.Spec.InitContainers[i].Resources.Requests.StorageEphemeral()
  544. }
  545. }
  546. }
  547. if containerValue.Cmp(initValue) > 0 {
  548. return containerValue
  549. }
  550. return initValue
  551. }
  552. // exceedDiskRequests compares whether or not pods' disk usage exceeds their requests
  553. func exceedDiskRequests(stats statsFunc, fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) cmpFunc {
  554. return func(p1, p2 *v1.Pod) int {
  555. p1Stats, p1Found := stats(p1)
  556. p2Stats, p2Found := stats(p2)
  557. if !p1Found || !p2Found {
  558. // prioritize evicting the pod for which no stats were found
  559. return cmpBool(!p1Found, !p2Found)
  560. }
  561. p1Usage, p1Err := podDiskUsage(p1Stats, p1, fsStatsToMeasure)
  562. p2Usage, p2Err := podDiskUsage(p2Stats, p2, fsStatsToMeasure)
  563. if p1Err != nil || p2Err != nil {
  564. // prioritize evicting the pod which had an error getting stats
  565. return cmpBool(p1Err != nil, p2Err != nil)
  566. }
  567. p1Disk := p1Usage[diskResource]
  568. p2Disk := p2Usage[diskResource]
  569. p1ExceedsRequests := p1Disk.Cmp(podRequest(p1, diskResource)) == 1
  570. p2ExceedsRequests := p2Disk.Cmp(podRequest(p2, diskResource)) == 1
  571. // prioritize evicting the pod which exceeds its requests
  572. return cmpBool(p1ExceedsRequests, p2ExceedsRequests)
  573. }
  574. }
  575. // disk compares pods by largest consumer of disk relative to request for the specified disk resource.
  576. func disk(stats statsFunc, fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) cmpFunc {
  577. return func(p1, p2 *v1.Pod) int {
  578. p1Stats, p1Found := stats(p1)
  579. p2Stats, p2Found := stats(p2)
  580. if !p1Found || !p2Found {
  581. // prioritize evicting the pod for which no stats were found
  582. return cmpBool(!p1Found, !p2Found)
  583. }
  584. p1Usage, p1Err := podDiskUsage(p1Stats, p1, fsStatsToMeasure)
  585. p2Usage, p2Err := podDiskUsage(p2Stats, p2, fsStatsToMeasure)
  586. if p1Err != nil || p2Err != nil {
  587. // prioritize evicting the pod which had an error getting stats
  588. return cmpBool(p1Err != nil, p2Err != nil)
  589. }
  590. // adjust p1, p2 usage relative to the request (if any)
  591. p1Disk := p1Usage[diskResource]
  592. p2Disk := p2Usage[diskResource]
  593. p1Request := podRequest(p1, v1.ResourceEphemeralStorage)
  594. p1Disk.Sub(p1Request)
  595. p2Request := podRequest(p2, v1.ResourceEphemeralStorage)
  596. p2Disk.Sub(p2Request)
  597. // prioritize evicting the pod which has the larger consumption of disk
  598. return p2Disk.Cmp(p1Disk)
  599. }
  600. }
  601. // cmpBool compares booleans, placing true before false
  602. func cmpBool(a, b bool) int {
  603. if a == b {
  604. return 0
  605. }
  606. if !b {
  607. return -1
  608. }
  609. return 1
  610. }
  611. // rankMemoryPressure orders the input pods for eviction in response to memory pressure.
  612. // It ranks by whether or not the pod's usage exceeds its requests, then by priority, and
  613. // finally by memory usage above requests.
  614. func rankMemoryPressure(pods []*v1.Pod, stats statsFunc) {
  615. orderedBy(exceedMemoryRequests(stats), priority, memory(stats)).Sort(pods)
  616. }
  617. // rankPIDPressure orders the input pods by priority in response to PID pressure.
  618. func rankPIDPressure(pods []*v1.Pod, stats statsFunc) {
  619. orderedBy(priority).Sort(pods)
  620. }
  621. // rankDiskPressureFunc returns a rankFunc that measures the specified fs stats.
  622. func rankDiskPressureFunc(fsStatsToMeasure []fsStatsType, diskResource v1.ResourceName) rankFunc {
  623. return func(pods []*v1.Pod, stats statsFunc) {
  624. orderedBy(exceedDiskRequests(stats, fsStatsToMeasure, diskResource), priority, disk(stats, fsStatsToMeasure, diskResource)).Sort(pods)
  625. }
  626. }
  627. // byEvictionPriority implements sort.Interface for []v1.ResourceName.
  628. type byEvictionPriority []evictionapi.Threshold
  629. func (a byEvictionPriority) Len() int { return len(a) }
  630. func (a byEvictionPriority) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  631. // Less ranks memory before all other resources, and ranks thresholds with no resource to reclaim last
  632. func (a byEvictionPriority) Less(i, j int) bool {
  633. _, jSignalHasResource := signalToResource[a[j].Signal]
  634. return a[i].Signal == evictionapi.SignalMemoryAvailable || a[i].Signal == evictionapi.SignalAllocatableMemoryAvailable || !jSignalHasResource
  635. }
  636. // makeSignalObservations derives observations using the specified summary provider.
  637. func makeSignalObservations(summary *statsapi.Summary) (signalObservations, statsFunc) {
  638. // build the function to work against for pod stats
  639. statsFunc := cachedStatsFunc(summary.Pods)
  640. // build an evaluation context for current eviction signals
  641. result := signalObservations{}
  642. if memory := summary.Node.Memory; memory != nil && memory.AvailableBytes != nil && memory.WorkingSetBytes != nil {
  643. result[evictionapi.SignalMemoryAvailable] = signalObservation{
  644. available: resource.NewQuantity(int64(*memory.AvailableBytes), resource.BinarySI),
  645. capacity: resource.NewQuantity(int64(*memory.AvailableBytes+*memory.WorkingSetBytes), resource.BinarySI),
  646. time: memory.Time,
  647. }
  648. }
  649. if allocatableContainer, err := getSysContainer(summary.Node.SystemContainers, statsapi.SystemContainerPods); err != nil {
  650. klog.Errorf("eviction manager: failed to construct signal: %q error: %v", evictionapi.SignalAllocatableMemoryAvailable, err)
  651. } else {
  652. if memory := allocatableContainer.Memory; memory != nil && memory.AvailableBytes != nil && memory.WorkingSetBytes != nil {
  653. result[evictionapi.SignalAllocatableMemoryAvailable] = signalObservation{
  654. available: resource.NewQuantity(int64(*memory.AvailableBytes), resource.BinarySI),
  655. capacity: resource.NewQuantity(int64(*memory.AvailableBytes+*memory.WorkingSetBytes), resource.BinarySI),
  656. time: memory.Time,
  657. }
  658. }
  659. }
  660. if nodeFs := summary.Node.Fs; nodeFs != nil {
  661. if nodeFs.AvailableBytes != nil && nodeFs.CapacityBytes != nil {
  662. result[evictionapi.SignalNodeFsAvailable] = signalObservation{
  663. available: resource.NewQuantity(int64(*nodeFs.AvailableBytes), resource.BinarySI),
  664. capacity: resource.NewQuantity(int64(*nodeFs.CapacityBytes), resource.BinarySI),
  665. time: nodeFs.Time,
  666. }
  667. }
  668. if nodeFs.InodesFree != nil && nodeFs.Inodes != nil {
  669. result[evictionapi.SignalNodeFsInodesFree] = signalObservation{
  670. available: resource.NewQuantity(int64(*nodeFs.InodesFree), resource.DecimalSI),
  671. capacity: resource.NewQuantity(int64(*nodeFs.Inodes), resource.DecimalSI),
  672. time: nodeFs.Time,
  673. }
  674. }
  675. }
  676. if summary.Node.Runtime != nil {
  677. if imageFs := summary.Node.Runtime.ImageFs; imageFs != nil {
  678. if imageFs.AvailableBytes != nil && imageFs.CapacityBytes != nil {
  679. result[evictionapi.SignalImageFsAvailable] = signalObservation{
  680. available: resource.NewQuantity(int64(*imageFs.AvailableBytes), resource.BinarySI),
  681. capacity: resource.NewQuantity(int64(*imageFs.CapacityBytes), resource.BinarySI),
  682. time: imageFs.Time,
  683. }
  684. if imageFs.InodesFree != nil && imageFs.Inodes != nil {
  685. result[evictionapi.SignalImageFsInodesFree] = signalObservation{
  686. available: resource.NewQuantity(int64(*imageFs.InodesFree), resource.DecimalSI),
  687. capacity: resource.NewQuantity(int64(*imageFs.Inodes), resource.DecimalSI),
  688. time: imageFs.Time,
  689. }
  690. }
  691. }
  692. }
  693. }
  694. if rlimit := summary.Node.Rlimit; rlimit != nil {
  695. if rlimit.NumOfRunningProcesses != nil && rlimit.MaxPID != nil {
  696. available := int64(*rlimit.MaxPID) - int64(*rlimit.NumOfRunningProcesses)
  697. result[evictionapi.SignalPIDAvailable] = signalObservation{
  698. available: resource.NewQuantity(available, resource.BinarySI),
  699. capacity: resource.NewQuantity(int64(*rlimit.MaxPID), resource.BinarySI),
  700. time: rlimit.Time,
  701. }
  702. }
  703. }
  704. return result, statsFunc
  705. }
  706. func getSysContainer(sysContainers []statsapi.ContainerStats, name string) (*statsapi.ContainerStats, error) {
  707. for _, cont := range sysContainers {
  708. if cont.Name == name {
  709. return &cont, nil
  710. }
  711. }
  712. return nil, fmt.Errorf("system container %q not found in metrics", name)
  713. }
  714. // thresholdsMet returns the set of thresholds that were met independent of grace period
  715. func thresholdsMet(thresholds []evictionapi.Threshold, observations signalObservations, enforceMinReclaim bool) []evictionapi.Threshold {
  716. results := []evictionapi.Threshold{}
  717. for i := range thresholds {
  718. threshold := thresholds[i]
  719. observed, found := observations[threshold.Signal]
  720. if !found {
  721. klog.Warningf("eviction manager: no observation found for eviction signal %v", threshold.Signal)
  722. continue
  723. }
  724. // determine if we have met the specified threshold
  725. thresholdMet := false
  726. quantity := evictionapi.GetThresholdQuantity(threshold.Value, observed.capacity)
  727. // if enforceMinReclaim is specified, we compare relative to value - minreclaim
  728. if enforceMinReclaim && threshold.MinReclaim != nil {
  729. quantity.Add(*evictionapi.GetThresholdQuantity(*threshold.MinReclaim, observed.capacity))
  730. }
  731. thresholdResult := quantity.Cmp(*observed.available)
  732. switch threshold.Operator {
  733. case evictionapi.OpLessThan:
  734. thresholdMet = thresholdResult > 0
  735. }
  736. if thresholdMet {
  737. results = append(results, threshold)
  738. }
  739. }
  740. return results
  741. }
  742. func debugLogObservations(logPrefix string, observations signalObservations) {
  743. if !klog.V(3) {
  744. return
  745. }
  746. for k, v := range observations {
  747. if !v.time.IsZero() {
  748. klog.Infof("eviction manager: %v: signal=%v, available: %v, capacity: %v, time: %v", logPrefix, k, v.available, v.capacity, v.time)
  749. } else {
  750. klog.Infof("eviction manager: %v: signal=%v, available: %v, capacity: %v", logPrefix, k, v.available, v.capacity)
  751. }
  752. }
  753. }
  754. func debugLogThresholdsWithObservation(logPrefix string, thresholds []evictionapi.Threshold, observations signalObservations) {
  755. if !klog.V(3) {
  756. return
  757. }
  758. for i := range thresholds {
  759. threshold := thresholds[i]
  760. observed, found := observations[threshold.Signal]
  761. if found {
  762. quantity := evictionapi.GetThresholdQuantity(threshold.Value, observed.capacity)
  763. klog.Infof("eviction manager: %v: threshold [signal=%v, quantity=%v] observed %v", logPrefix, threshold.Signal, quantity, observed.available)
  764. } else {
  765. klog.Infof("eviction manager: %v: threshold [signal=%v] had no observation", logPrefix, threshold.Signal)
  766. }
  767. }
  768. }
  769. func thresholdsUpdatedStats(thresholds []evictionapi.Threshold, observations, lastObservations signalObservations) []evictionapi.Threshold {
  770. results := []evictionapi.Threshold{}
  771. for i := range thresholds {
  772. threshold := thresholds[i]
  773. observed, found := observations[threshold.Signal]
  774. if !found {
  775. klog.Warningf("eviction manager: no observation found for eviction signal %v", threshold.Signal)
  776. continue
  777. }
  778. last, found := lastObservations[threshold.Signal]
  779. if !found || observed.time.IsZero() || observed.time.After(last.time.Time) {
  780. results = append(results, threshold)
  781. }
  782. }
  783. return results
  784. }
  785. // thresholdsFirstObservedAt merges the input set of thresholds with the previous observation to determine when active set of thresholds were initially met.
  786. func thresholdsFirstObservedAt(thresholds []evictionapi.Threshold, lastObservedAt thresholdsObservedAt, now time.Time) thresholdsObservedAt {
  787. results := thresholdsObservedAt{}
  788. for i := range thresholds {
  789. observedAt, found := lastObservedAt[thresholds[i]]
  790. if !found {
  791. observedAt = now
  792. }
  793. results[thresholds[i]] = observedAt
  794. }
  795. return results
  796. }
  797. // thresholdsMetGracePeriod returns the set of thresholds that have satisfied associated grace period
  798. func thresholdsMetGracePeriod(observedAt thresholdsObservedAt, now time.Time) []evictionapi.Threshold {
  799. results := []evictionapi.Threshold{}
  800. for threshold, at := range observedAt {
  801. duration := now.Sub(at)
  802. if duration < threshold.GracePeriod {
  803. klog.V(2).Infof("eviction manager: eviction criteria not yet met for %v, duration: %v", formatThreshold(threshold), duration)
  804. continue
  805. }
  806. results = append(results, threshold)
  807. }
  808. return results
  809. }
  810. // nodeConditions returns the set of node conditions associated with a threshold
  811. func nodeConditions(thresholds []evictionapi.Threshold) []v1.NodeConditionType {
  812. results := []v1.NodeConditionType{}
  813. for _, threshold := range thresholds {
  814. if nodeCondition, found := signalToNodeCondition[threshold.Signal]; found {
  815. if !hasNodeCondition(results, nodeCondition) {
  816. results = append(results, nodeCondition)
  817. }
  818. }
  819. }
  820. return results
  821. }
  822. // nodeConditionsLastObservedAt merges the input with the previous observation to determine when a condition was most recently met.
  823. func nodeConditionsLastObservedAt(nodeConditions []v1.NodeConditionType, lastObservedAt nodeConditionsObservedAt, now time.Time) nodeConditionsObservedAt {
  824. results := nodeConditionsObservedAt{}
  825. // the input conditions were observed "now"
  826. for i := range nodeConditions {
  827. results[nodeConditions[i]] = now
  828. }
  829. // the conditions that were not observed now are merged in with their old time
  830. for key, value := range lastObservedAt {
  831. _, found := results[key]
  832. if !found {
  833. results[key] = value
  834. }
  835. }
  836. return results
  837. }
  838. // nodeConditionsObservedSince returns the set of conditions that have been observed within the specified period
  839. func nodeConditionsObservedSince(observedAt nodeConditionsObservedAt, period time.Duration, now time.Time) []v1.NodeConditionType {
  840. results := []v1.NodeConditionType{}
  841. for nodeCondition, at := range observedAt {
  842. duration := now.Sub(at)
  843. if duration < period {
  844. results = append(results, nodeCondition)
  845. }
  846. }
  847. return results
  848. }
  849. // hasFsStatsType returns true if the fsStat is in the input list
  850. func hasFsStatsType(inputs []fsStatsType, item fsStatsType) bool {
  851. for _, input := range inputs {
  852. if input == item {
  853. return true
  854. }
  855. }
  856. return false
  857. }
  858. // hasNodeCondition returns true if the node condition is in the input list
  859. func hasNodeCondition(inputs []v1.NodeConditionType, item v1.NodeConditionType) bool {
  860. for _, input := range inputs {
  861. if input == item {
  862. return true
  863. }
  864. }
  865. return false
  866. }
  867. // mergeThresholds will merge both threshold lists eliminating duplicates.
  868. func mergeThresholds(inputsA []evictionapi.Threshold, inputsB []evictionapi.Threshold) []evictionapi.Threshold {
  869. results := inputsA
  870. for _, threshold := range inputsB {
  871. if !hasThreshold(results, threshold) {
  872. results = append(results, threshold)
  873. }
  874. }
  875. return results
  876. }
  877. // hasThreshold returns true if the threshold is in the input list
  878. func hasThreshold(inputs []evictionapi.Threshold, item evictionapi.Threshold) bool {
  879. for _, input := range inputs {
  880. if input.GracePeriod == item.GracePeriod && input.Operator == item.Operator && input.Signal == item.Signal && compareThresholdValue(input.Value, item.Value) {
  881. return true
  882. }
  883. }
  884. return false
  885. }
  886. // compareThresholdValue returns true if the two thresholdValue objects are logically the same
  887. func compareThresholdValue(a evictionapi.ThresholdValue, b evictionapi.ThresholdValue) bool {
  888. if a.Quantity != nil {
  889. if b.Quantity == nil {
  890. return false
  891. }
  892. return a.Quantity.Cmp(*b.Quantity) == 0
  893. }
  894. if b.Quantity != nil {
  895. return false
  896. }
  897. return a.Percentage == b.Percentage
  898. }
  899. // isHardEvictionThreshold returns true if eviction should immediately occur
  900. func isHardEvictionThreshold(threshold evictionapi.Threshold) bool {
  901. return threshold.GracePeriod == time.Duration(0)
  902. }
  903. func isAllocatableEvictionThreshold(threshold evictionapi.Threshold) bool {
  904. return threshold.Signal == evictionapi.SignalAllocatableMemoryAvailable
  905. }
  906. // buildSignalToRankFunc returns ranking functions associated with resources
  907. func buildSignalToRankFunc(withImageFs bool) map[evictionapi.Signal]rankFunc {
  908. signalToRankFunc := map[evictionapi.Signal]rankFunc{
  909. evictionapi.SignalMemoryAvailable: rankMemoryPressure,
  910. evictionapi.SignalAllocatableMemoryAvailable: rankMemoryPressure,
  911. evictionapi.SignalPIDAvailable: rankPIDPressure,
  912. }
  913. // usage of an imagefs is optional
  914. if withImageFs {
  915. // with an imagefs, nodefs pod rank func for eviction only includes logs and local volumes
  916. signalToRankFunc[evictionapi.SignalNodeFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)
  917. signalToRankFunc[evictionapi.SignalNodeFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)
  918. // with an imagefs, imagefs pod rank func for eviction only includes rootfs
  919. signalToRankFunc[evictionapi.SignalImageFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot}, v1.ResourceEphemeralStorage)
  920. signalToRankFunc[evictionapi.SignalImageFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot}, resourceInodes)
  921. } else {
  922. // without an imagefs, nodefs pod rank func for eviction looks at all fs stats.
  923. // since imagefs and nodefs share a common device, they share common ranking functions.
  924. signalToRankFunc[evictionapi.SignalNodeFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)
  925. signalToRankFunc[evictionapi.SignalNodeFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)
  926. signalToRankFunc[evictionapi.SignalImageFsAvailable] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, v1.ResourceEphemeralStorage)
  927. signalToRankFunc[evictionapi.SignalImageFsInodesFree] = rankDiskPressureFunc([]fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}, resourceInodes)
  928. }
  929. return signalToRankFunc
  930. }
  931. // PodIsEvicted returns true if the reported pod status is due to an eviction.
  932. func PodIsEvicted(podStatus v1.PodStatus) bool {
  933. return podStatus.Phase == v1.PodFailed && podStatus.Reason == Reason
  934. }
  935. // buildSignalToNodeReclaimFuncs returns reclaim functions associated with resources.
  936. func buildSignalToNodeReclaimFuncs(imageGC ImageGC, containerGC ContainerGC, withImageFs bool) map[evictionapi.Signal]nodeReclaimFuncs {
  937. signalToReclaimFunc := map[evictionapi.Signal]nodeReclaimFuncs{}
  938. // usage of an imagefs is optional
  939. if withImageFs {
  940. // with an imagefs, nodefs pressure should just delete logs
  941. signalToReclaimFunc[evictionapi.SignalNodeFsAvailable] = nodeReclaimFuncs{}
  942. signalToReclaimFunc[evictionapi.SignalNodeFsInodesFree] = nodeReclaimFuncs{}
  943. // with an imagefs, imagefs pressure should delete unused images
  944. signalToReclaimFunc[evictionapi.SignalImageFsAvailable] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages}
  945. signalToReclaimFunc[evictionapi.SignalImageFsInodesFree] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages}
  946. } else {
  947. // without an imagefs, nodefs pressure should delete logs, and unused images
  948. // since imagefs and nodefs share a common device, they share common reclaim functions
  949. signalToReclaimFunc[evictionapi.SignalNodeFsAvailable] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages}
  950. signalToReclaimFunc[evictionapi.SignalNodeFsInodesFree] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages}
  951. signalToReclaimFunc[evictionapi.SignalImageFsAvailable] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages}
  952. signalToReclaimFunc[evictionapi.SignalImageFsInodesFree] = nodeReclaimFuncs{containerGC.DeleteAllUnusedContainers, imageGC.DeleteUnusedImages}
  953. }
  954. return signalToReclaimFunc
  955. }
  956. // evictionMessage constructs a useful message about why an eviction occurred, and annotations to provide metadata about the eviction
  957. func evictionMessage(resourceToReclaim v1.ResourceName, pod *v1.Pod, stats statsFunc) (message string, annotations map[string]string) {
  958. annotations = make(map[string]string)
  959. message = fmt.Sprintf(nodeLowMessageFmt, resourceToReclaim)
  960. containers := []string{}
  961. containerUsage := []string{}
  962. podStats, ok := stats(pod)
  963. if !ok {
  964. return
  965. }
  966. for _, containerStats := range podStats.Containers {
  967. for _, container := range pod.Spec.Containers {
  968. if container.Name == containerStats.Name {
  969. requests := container.Resources.Requests[resourceToReclaim]
  970. var usage *resource.Quantity
  971. switch resourceToReclaim {
  972. case v1.ResourceEphemeralStorage:
  973. if containerStats.Rootfs != nil && containerStats.Rootfs.UsedBytes != nil && containerStats.Logs != nil && containerStats.Logs.UsedBytes != nil {
  974. usage = resource.NewQuantity(int64(*containerStats.Rootfs.UsedBytes+*containerStats.Logs.UsedBytes), resource.BinarySI)
  975. }
  976. case v1.ResourceMemory:
  977. if containerStats.Memory != nil && containerStats.Memory.WorkingSetBytes != nil {
  978. usage = resource.NewQuantity(int64(*containerStats.Memory.WorkingSetBytes), resource.BinarySI)
  979. }
  980. }
  981. if usage != nil && usage.Cmp(requests) > 0 {
  982. message += fmt.Sprintf(containerMessageFmt, container.Name, usage.String(), requests.String())
  983. containers = append(containers, container.Name)
  984. containerUsage = append(containerUsage, usage.String())
  985. }
  986. }
  987. }
  988. }
  989. annotations[OffendingContainersKey] = strings.Join(containers, ",")
  990. annotations[OffendingContainersUsageKey] = strings.Join(containerUsage, ",")
  991. annotations[StarvedResourceKey] = string(resourceToReclaim)
  992. return
  993. }