eviction_manager.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589
  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. "sync"
  18. "time"
  19. "k8s.io/klog"
  20. v1 "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/api/resource"
  22. "k8s.io/apimachinery/pkg/util/clock"
  23. utilfeature "k8s.io/apiserver/pkg/util/feature"
  24. "k8s.io/client-go/tools/record"
  25. apiv1resource "k8s.io/kubernetes/pkg/api/v1/resource"
  26. v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
  27. v1qos "k8s.io/kubernetes/pkg/apis/core/v1/helper/qos"
  28. "k8s.io/kubernetes/pkg/features"
  29. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  30. evictionapi "k8s.io/kubernetes/pkg/kubelet/eviction/api"
  31. "k8s.io/kubernetes/pkg/kubelet/lifecycle"
  32. "k8s.io/kubernetes/pkg/kubelet/metrics"
  33. "k8s.io/kubernetes/pkg/kubelet/server/stats"
  34. kubelettypes "k8s.io/kubernetes/pkg/kubelet/types"
  35. "k8s.io/kubernetes/pkg/kubelet/util/format"
  36. )
  37. const (
  38. podCleanupTimeout = 30 * time.Second
  39. podCleanupPollFreq = time.Second
  40. )
  41. const (
  42. // signalEphemeralContainerFsLimit is amount of storage available on filesystem requested by the container
  43. signalEphemeralContainerFsLimit string = "ephemeralcontainerfs.limit"
  44. // signalEphemeralPodFsLimit is amount of storage available on filesystem requested by the pod
  45. signalEphemeralPodFsLimit string = "ephemeralpodfs.limit"
  46. // signalEmptyDirFsLimit is amount of storage available on filesystem requested by an emptyDir
  47. signalEmptyDirFsLimit string = "emptydirfs.limit"
  48. )
  49. // managerImpl implements Manager
  50. type managerImpl struct {
  51. // used to track time
  52. clock clock.Clock
  53. // config is how the manager is configured
  54. config Config
  55. // the function to invoke to kill a pod
  56. killPodFunc KillPodFunc
  57. // the function to get the mirror pod by a given statid pod
  58. mirrorPodFunc MirrorPodFunc
  59. // the interface that knows how to do image gc
  60. imageGC ImageGC
  61. // the interface that knows how to do container gc
  62. containerGC ContainerGC
  63. // protects access to internal state
  64. sync.RWMutex
  65. // node conditions are the set of conditions present
  66. nodeConditions []v1.NodeConditionType
  67. // captures when a node condition was last observed based on a threshold being met
  68. nodeConditionsLastObservedAt nodeConditionsObservedAt
  69. // nodeRef is a reference to the node
  70. nodeRef *v1.ObjectReference
  71. // used to record events about the node
  72. recorder record.EventRecorder
  73. // used to measure usage stats on system
  74. summaryProvider stats.SummaryProvider
  75. // records when a threshold was first observed
  76. thresholdsFirstObservedAt thresholdsObservedAt
  77. // records the set of thresholds that have been met (including graceperiod) but not yet resolved
  78. thresholdsMet []evictionapi.Threshold
  79. // signalToRankFunc maps a resource to ranking function for that resource.
  80. signalToRankFunc map[evictionapi.Signal]rankFunc
  81. // signalToNodeReclaimFuncs maps a resource to an ordered list of functions that know how to reclaim that resource.
  82. signalToNodeReclaimFuncs map[evictionapi.Signal]nodeReclaimFuncs
  83. // last observations from synchronize
  84. lastObservations signalObservations
  85. // dedicatedImageFs indicates if imagefs is on a separate device from the rootfs
  86. dedicatedImageFs *bool
  87. // thresholdNotifiers is a list of memory threshold notifiers which each notify for a memory eviction threshold
  88. thresholdNotifiers []ThresholdNotifier
  89. // thresholdsLastUpdated is the last time the thresholdNotifiers were updated.
  90. thresholdsLastUpdated time.Time
  91. }
  92. // ensure it implements the required interface
  93. var _ Manager = &managerImpl{}
  94. // NewManager returns a configured Manager and an associated admission handler to enforce eviction configuration.
  95. func NewManager(
  96. summaryProvider stats.SummaryProvider,
  97. config Config,
  98. killPodFunc KillPodFunc,
  99. mirrorPodFunc MirrorPodFunc,
  100. imageGC ImageGC,
  101. containerGC ContainerGC,
  102. recorder record.EventRecorder,
  103. nodeRef *v1.ObjectReference,
  104. clock clock.Clock,
  105. ) (Manager, lifecycle.PodAdmitHandler) {
  106. manager := &managerImpl{
  107. clock: clock,
  108. killPodFunc: killPodFunc,
  109. mirrorPodFunc: mirrorPodFunc,
  110. imageGC: imageGC,
  111. containerGC: containerGC,
  112. config: config,
  113. recorder: recorder,
  114. summaryProvider: summaryProvider,
  115. nodeRef: nodeRef,
  116. nodeConditionsLastObservedAt: nodeConditionsObservedAt{},
  117. thresholdsFirstObservedAt: thresholdsObservedAt{},
  118. dedicatedImageFs: nil,
  119. thresholdNotifiers: []ThresholdNotifier{},
  120. }
  121. return manager, manager
  122. }
  123. // Admit rejects a pod if its not safe to admit for node stability.
  124. func (m *managerImpl) Admit(attrs *lifecycle.PodAdmitAttributes) lifecycle.PodAdmitResult {
  125. m.RLock()
  126. defer m.RUnlock()
  127. if len(m.nodeConditions) == 0 {
  128. return lifecycle.PodAdmitResult{Admit: true}
  129. }
  130. // Admit Critical pods even under resource pressure since they are required for system stability.
  131. // https://github.com/kubernetes/kubernetes/issues/40573 has more details.
  132. if kubelettypes.IsCriticalPod(attrs.Pod) {
  133. return lifecycle.PodAdmitResult{Admit: true}
  134. }
  135. // Conditions other than memory pressure reject all pods
  136. nodeOnlyHasMemoryPressureCondition := hasNodeCondition(m.nodeConditions, v1.NodeMemoryPressure) && len(m.nodeConditions) == 1
  137. if nodeOnlyHasMemoryPressureCondition {
  138. notBestEffort := v1.PodQOSBestEffort != v1qos.GetPodQOS(attrs.Pod)
  139. if notBestEffort {
  140. return lifecycle.PodAdmitResult{Admit: true}
  141. }
  142. // When node has memory pressure, check BestEffort Pod's toleration:
  143. // admit it if tolerates memory pressure taint, fail for other tolerations, e.g. DiskPressure.
  144. if v1helper.TolerationsTolerateTaint(attrs.Pod.Spec.Tolerations, &v1.Taint{
  145. Key: v1.TaintNodeMemoryPressure,
  146. Effect: v1.TaintEffectNoSchedule,
  147. }) {
  148. return lifecycle.PodAdmitResult{Admit: true}
  149. }
  150. }
  151. // reject pods when under memory pressure (if pod is best effort), or if under disk pressure.
  152. klog.Warningf("Failed to admit pod %s - node has conditions: %v", format.Pod(attrs.Pod), m.nodeConditions)
  153. return lifecycle.PodAdmitResult{
  154. Admit: false,
  155. Reason: Reason,
  156. Message: fmt.Sprintf(nodeConditionMessageFmt, m.nodeConditions),
  157. }
  158. }
  159. // Start starts the control loop to observe and response to low compute resources.
  160. func (m *managerImpl) Start(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc, podCleanedUpFunc PodCleanedUpFunc, monitoringInterval time.Duration) {
  161. thresholdHandler := func(message string) {
  162. klog.Infof(message)
  163. m.synchronize(diskInfoProvider, podFunc)
  164. }
  165. if m.config.KernelMemcgNotification {
  166. for _, threshold := range m.config.Thresholds {
  167. if threshold.Signal == evictionapi.SignalMemoryAvailable || threshold.Signal == evictionapi.SignalAllocatableMemoryAvailable {
  168. notifier, err := NewMemoryThresholdNotifier(threshold, m.config.PodCgroupRoot, &CgroupNotifierFactory{}, thresholdHandler)
  169. if err != nil {
  170. klog.Warningf("eviction manager: failed to create memory threshold notifier: %v", err)
  171. } else {
  172. go notifier.Start()
  173. m.thresholdNotifiers = append(m.thresholdNotifiers, notifier)
  174. }
  175. }
  176. }
  177. }
  178. // start the eviction manager monitoring
  179. go func() {
  180. for {
  181. if evictedPods := m.synchronize(diskInfoProvider, podFunc); evictedPods != nil {
  182. klog.Infof("eviction manager: pods %s evicted, waiting for pod to be cleaned up", format.Pods(evictedPods))
  183. m.waitForPodsCleanup(podCleanedUpFunc, evictedPods)
  184. } else {
  185. time.Sleep(monitoringInterval)
  186. }
  187. }
  188. }()
  189. }
  190. // IsUnderMemoryPressure returns true if the node is under memory pressure.
  191. func (m *managerImpl) IsUnderMemoryPressure() bool {
  192. m.RLock()
  193. defer m.RUnlock()
  194. return hasNodeCondition(m.nodeConditions, v1.NodeMemoryPressure)
  195. }
  196. // IsUnderDiskPressure returns true if the node is under disk pressure.
  197. func (m *managerImpl) IsUnderDiskPressure() bool {
  198. m.RLock()
  199. defer m.RUnlock()
  200. return hasNodeCondition(m.nodeConditions, v1.NodeDiskPressure)
  201. }
  202. // IsUnderPIDPressure returns true if the node is under PID pressure.
  203. func (m *managerImpl) IsUnderPIDPressure() bool {
  204. m.RLock()
  205. defer m.RUnlock()
  206. return hasNodeCondition(m.nodeConditions, v1.NodePIDPressure)
  207. }
  208. // synchronize is the main control loop that enforces eviction thresholds.
  209. // Returns the pod that was killed, or nil if no pod was killed.
  210. func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc ActivePodsFunc) []*v1.Pod {
  211. // if we have nothing to do, just return
  212. thresholds := m.config.Thresholds
  213. if len(thresholds) == 0 && !utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
  214. return nil
  215. }
  216. klog.V(3).Infof("eviction manager: synchronize housekeeping")
  217. // build the ranking functions (if not yet known)
  218. // TODO: have a function in cadvisor that lets us know if global housekeeping has completed
  219. if m.dedicatedImageFs == nil {
  220. hasImageFs, ok := diskInfoProvider.HasDedicatedImageFs()
  221. if ok != nil {
  222. return nil
  223. }
  224. m.dedicatedImageFs = &hasImageFs
  225. m.signalToRankFunc = buildSignalToRankFunc(hasImageFs)
  226. m.signalToNodeReclaimFuncs = buildSignalToNodeReclaimFuncs(m.imageGC, m.containerGC, hasImageFs)
  227. }
  228. activePods := podFunc()
  229. updateStats := true
  230. summary, err := m.summaryProvider.Get(updateStats)
  231. if err != nil {
  232. klog.Errorf("eviction manager: failed to get summary stats: %v", err)
  233. return nil
  234. }
  235. if m.clock.Since(m.thresholdsLastUpdated) > notifierRefreshInterval {
  236. m.thresholdsLastUpdated = m.clock.Now()
  237. for _, notifier := range m.thresholdNotifiers {
  238. if err := notifier.UpdateThreshold(summary); err != nil {
  239. klog.Warningf("eviction manager: failed to update %s: %v", notifier.Description(), err)
  240. }
  241. }
  242. }
  243. // make observations and get a function to derive pod usage stats relative to those observations.
  244. observations, statsFunc := makeSignalObservations(summary)
  245. debugLogObservations("observations", observations)
  246. // determine the set of thresholds met independent of grace period
  247. thresholds = thresholdsMet(thresholds, observations, false)
  248. debugLogThresholdsWithObservation("thresholds - ignoring grace period", thresholds, observations)
  249. // determine the set of thresholds previously met that have not yet satisfied the associated min-reclaim
  250. if len(m.thresholdsMet) > 0 {
  251. thresholdsNotYetResolved := thresholdsMet(m.thresholdsMet, observations, true)
  252. thresholds = mergeThresholds(thresholds, thresholdsNotYetResolved)
  253. }
  254. debugLogThresholdsWithObservation("thresholds - reclaim not satisfied", thresholds, observations)
  255. // track when a threshold was first observed
  256. now := m.clock.Now()
  257. thresholdsFirstObservedAt := thresholdsFirstObservedAt(thresholds, m.thresholdsFirstObservedAt, now)
  258. // the set of node conditions that are triggered by currently observed thresholds
  259. nodeConditions := nodeConditions(thresholds)
  260. if len(nodeConditions) > 0 {
  261. klog.V(3).Infof("eviction manager: node conditions - observed: %v", nodeConditions)
  262. }
  263. // track when a node condition was last observed
  264. nodeConditionsLastObservedAt := nodeConditionsLastObservedAt(nodeConditions, m.nodeConditionsLastObservedAt, now)
  265. // node conditions report true if it has been observed within the transition period window
  266. nodeConditions = nodeConditionsObservedSince(nodeConditionsLastObservedAt, m.config.PressureTransitionPeriod, now)
  267. if len(nodeConditions) > 0 {
  268. klog.V(3).Infof("eviction manager: node conditions - transition period not met: %v", nodeConditions)
  269. }
  270. // determine the set of thresholds we need to drive eviction behavior (i.e. all grace periods are met)
  271. thresholds = thresholdsMetGracePeriod(thresholdsFirstObservedAt, now)
  272. debugLogThresholdsWithObservation("thresholds - grace periods satisfied", thresholds, observations)
  273. // update internal state
  274. m.Lock()
  275. m.nodeConditions = nodeConditions
  276. m.thresholdsFirstObservedAt = thresholdsFirstObservedAt
  277. m.nodeConditionsLastObservedAt = nodeConditionsLastObservedAt
  278. m.thresholdsMet = thresholds
  279. // determine the set of thresholds whose stats have been updated since the last sync
  280. thresholds = thresholdsUpdatedStats(thresholds, observations, m.lastObservations)
  281. debugLogThresholdsWithObservation("thresholds - updated stats", thresholds, observations)
  282. m.lastObservations = observations
  283. m.Unlock()
  284. // evict pods if there is a resource usage violation from local volume temporary storage
  285. // If eviction happens in localStorageEviction function, skip the rest of eviction action
  286. if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
  287. if evictedPods := m.localStorageEviction(summary, activePods); len(evictedPods) > 0 {
  288. return evictedPods
  289. }
  290. }
  291. if len(thresholds) == 0 {
  292. klog.V(3).Infof("eviction manager: no resources are starved")
  293. return nil
  294. }
  295. // rank the thresholds by eviction priority
  296. sort.Sort(byEvictionPriority(thresholds))
  297. thresholdToReclaim, resourceToReclaim, foundAny := getReclaimableThreshold(thresholds)
  298. if !foundAny {
  299. return nil
  300. }
  301. klog.Warningf("eviction manager: attempting to reclaim %v", resourceToReclaim)
  302. // record an event about the resources we are now attempting to reclaim via eviction
  303. m.recorder.Eventf(m.nodeRef, v1.EventTypeWarning, "EvictionThresholdMet", "Attempting to reclaim %s", resourceToReclaim)
  304. // check if there are node-level resources we can reclaim to reduce pressure before evicting end-user pods.
  305. if m.reclaimNodeLevelResources(thresholdToReclaim.Signal, resourceToReclaim) {
  306. klog.Infof("eviction manager: able to reduce %v pressure without evicting pods.", resourceToReclaim)
  307. return nil
  308. }
  309. klog.Infof("eviction manager: must evict pod(s) to reclaim %v", resourceToReclaim)
  310. // rank the pods for eviction
  311. rank, ok := m.signalToRankFunc[thresholdToReclaim.Signal]
  312. if !ok {
  313. klog.Errorf("eviction manager: no ranking function for signal %s", thresholdToReclaim.Signal)
  314. return nil
  315. }
  316. // the only candidates viable for eviction are those pods that had anything running.
  317. if len(activePods) == 0 {
  318. klog.Errorf("eviction manager: eviction thresholds have been met, but no pods are active to evict")
  319. return nil
  320. }
  321. // rank the running pods for eviction for the specified resource
  322. rank(activePods, statsFunc)
  323. klog.Infof("eviction manager: pods ranked for eviction: %s", format.Pods(activePods))
  324. //record age of metrics for met thresholds that we are using for evictions.
  325. for _, t := range thresholds {
  326. timeObserved := observations[t.Signal].time
  327. if !timeObserved.IsZero() {
  328. metrics.EvictionStatsAge.WithLabelValues(string(t.Signal)).Observe(metrics.SinceInSeconds(timeObserved.Time))
  329. }
  330. }
  331. // we kill at most a single pod during each eviction interval
  332. for i := range activePods {
  333. pod := activePods[i]
  334. gracePeriodOverride := int64(0)
  335. if !isHardEvictionThreshold(thresholdToReclaim) {
  336. gracePeriodOverride = m.config.MaxPodGracePeriodSeconds
  337. }
  338. message, annotations := evictionMessage(resourceToReclaim, pod, statsFunc)
  339. if m.evictPod(pod, gracePeriodOverride, message, annotations) {
  340. metrics.Evictions.WithLabelValues(string(thresholdToReclaim.Signal)).Inc()
  341. return []*v1.Pod{pod}
  342. }
  343. }
  344. klog.Infof("eviction manager: unable to evict any pods from the node")
  345. return nil
  346. }
  347. func (m *managerImpl) waitForPodsCleanup(podCleanedUpFunc PodCleanedUpFunc, pods []*v1.Pod) {
  348. timeout := m.clock.NewTimer(podCleanupTimeout)
  349. defer timeout.Stop()
  350. ticker := m.clock.NewTicker(podCleanupPollFreq)
  351. defer ticker.Stop()
  352. for {
  353. select {
  354. case <-timeout.C():
  355. klog.Warningf("eviction manager: timed out waiting for pods %s to be cleaned up", format.Pods(pods))
  356. return
  357. case <-ticker.C():
  358. for i, pod := range pods {
  359. if !podCleanedUpFunc(pod) {
  360. break
  361. }
  362. if i == len(pods)-1 {
  363. klog.Infof("eviction manager: pods %s successfully cleaned up", format.Pods(pods))
  364. return
  365. }
  366. }
  367. }
  368. }
  369. }
  370. // reclaimNodeLevelResources attempts to reclaim node level resources. returns true if thresholds were satisfied and no pod eviction is required.
  371. func (m *managerImpl) reclaimNodeLevelResources(signalToReclaim evictionapi.Signal, resourceToReclaim v1.ResourceName) bool {
  372. nodeReclaimFuncs := m.signalToNodeReclaimFuncs[signalToReclaim]
  373. for _, nodeReclaimFunc := range nodeReclaimFuncs {
  374. // attempt to reclaim the pressured resource.
  375. if err := nodeReclaimFunc(); err != nil {
  376. klog.Warningf("eviction manager: unexpected error when attempting to reduce %v pressure: %v", resourceToReclaim, err)
  377. }
  378. }
  379. if len(nodeReclaimFuncs) > 0 {
  380. summary, err := m.summaryProvider.Get(true)
  381. if err != nil {
  382. klog.Errorf("eviction manager: failed to get summary stats after resource reclaim: %v", err)
  383. return false
  384. }
  385. // make observations and get a function to derive pod usage stats relative to those observations.
  386. observations, _ := makeSignalObservations(summary)
  387. debugLogObservations("observations after resource reclaim", observations)
  388. // determine the set of thresholds met independent of grace period
  389. thresholds := thresholdsMet(m.config.Thresholds, observations, false)
  390. debugLogThresholdsWithObservation("thresholds after resource reclaim - ignoring grace period", thresholds, observations)
  391. if len(thresholds) == 0 {
  392. return true
  393. }
  394. }
  395. return false
  396. }
  397. // localStorageEviction checks the EmptyDir volume usage for each pod and determine whether it exceeds the specified limit and needs
  398. // to be evicted. It also checks every container in the pod, if the container overlay usage exceeds the limit, the pod will be evicted too.
  399. func (m *managerImpl) localStorageEviction(summary *statsapi.Summary, pods []*v1.Pod) []*v1.Pod {
  400. statsFunc := cachedStatsFunc(summary.Pods)
  401. evicted := []*v1.Pod{}
  402. for _, pod := range pods {
  403. podStats, ok := statsFunc(pod)
  404. if !ok {
  405. continue
  406. }
  407. if m.emptyDirLimitEviction(podStats, pod) {
  408. evicted = append(evicted, pod)
  409. continue
  410. }
  411. if m.podEphemeralStorageLimitEviction(podStats, pod) {
  412. evicted = append(evicted, pod)
  413. continue
  414. }
  415. if m.containerEphemeralStorageLimitEviction(podStats, pod) {
  416. evicted = append(evicted, pod)
  417. }
  418. }
  419. return evicted
  420. }
  421. func (m *managerImpl) emptyDirLimitEviction(podStats statsapi.PodStats, pod *v1.Pod) bool {
  422. podVolumeUsed := make(map[string]*resource.Quantity)
  423. for _, volume := range podStats.VolumeStats {
  424. podVolumeUsed[volume.Name] = resource.NewQuantity(int64(*volume.UsedBytes), resource.BinarySI)
  425. }
  426. for i := range pod.Spec.Volumes {
  427. source := &pod.Spec.Volumes[i].VolumeSource
  428. if source.EmptyDir != nil {
  429. size := source.EmptyDir.SizeLimit
  430. used := podVolumeUsed[pod.Spec.Volumes[i].Name]
  431. if used != nil && size != nil && size.Sign() == 1 && used.Cmp(*size) > 0 {
  432. // the emptyDir usage exceeds the size limit, evict the pod
  433. if m.evictPod(pod, 0, fmt.Sprintf(emptyDirMessageFmt, pod.Spec.Volumes[i].Name, size.String()), nil) {
  434. metrics.Evictions.WithLabelValues(signalEmptyDirFsLimit).Inc()
  435. return true
  436. }
  437. return false
  438. }
  439. }
  440. }
  441. return false
  442. }
  443. func (m *managerImpl) podEphemeralStorageLimitEviction(podStats statsapi.PodStats, pod *v1.Pod) bool {
  444. _, podLimits := apiv1resource.PodRequestsAndLimits(pod)
  445. _, found := podLimits[v1.ResourceEphemeralStorage]
  446. if !found {
  447. return false
  448. }
  449. podEphemeralStorageTotalUsage := &resource.Quantity{}
  450. var fsStatsSet []fsStatsType
  451. if *m.dedicatedImageFs {
  452. fsStatsSet = []fsStatsType{fsStatsLogs, fsStatsLocalVolumeSource}
  453. } else {
  454. fsStatsSet = []fsStatsType{fsStatsRoot, fsStatsLogs, fsStatsLocalVolumeSource}
  455. }
  456. podEphemeralUsage, err := podLocalEphemeralStorageUsage(podStats, pod, fsStatsSet)
  457. if err != nil {
  458. klog.Errorf("eviction manager: error getting pod disk usage %v", err)
  459. return false
  460. }
  461. podEphemeralStorageTotalUsage.Add(podEphemeralUsage[v1.ResourceEphemeralStorage])
  462. podEphemeralStorageLimit := podLimits[v1.ResourceEphemeralStorage]
  463. if podEphemeralStorageTotalUsage.Cmp(podEphemeralStorageLimit) > 0 {
  464. // the total usage of pod exceeds the total size limit of containers, evict the pod
  465. if m.evictPod(pod, 0, fmt.Sprintf(podEphemeralStorageMessageFmt, podEphemeralStorageLimit.String()), nil) {
  466. metrics.Evictions.WithLabelValues(signalEphemeralPodFsLimit).Inc()
  467. return true
  468. }
  469. return false
  470. }
  471. return false
  472. }
  473. func (m *managerImpl) containerEphemeralStorageLimitEviction(podStats statsapi.PodStats, pod *v1.Pod) bool {
  474. thresholdsMap := make(map[string]*resource.Quantity)
  475. for _, container := range pod.Spec.Containers {
  476. ephemeralLimit := container.Resources.Limits.StorageEphemeral()
  477. if ephemeralLimit != nil && ephemeralLimit.Value() != 0 {
  478. thresholdsMap[container.Name] = ephemeralLimit
  479. }
  480. }
  481. for _, containerStat := range podStats.Containers {
  482. containerUsed := diskUsage(containerStat.Logs)
  483. if !*m.dedicatedImageFs {
  484. containerUsed.Add(*diskUsage(containerStat.Rootfs))
  485. }
  486. if ephemeralStorageThreshold, ok := thresholdsMap[containerStat.Name]; ok {
  487. if ephemeralStorageThreshold.Cmp(*containerUsed) < 0 {
  488. if m.evictPod(pod, 0, fmt.Sprintf(containerEphemeralStorageMessageFmt, containerStat.Name, ephemeralStorageThreshold.String()), nil) {
  489. metrics.Evictions.WithLabelValues(signalEphemeralContainerFsLimit).Inc()
  490. return true
  491. }
  492. return false
  493. }
  494. }
  495. }
  496. return false
  497. }
  498. func (m *managerImpl) evictPod(pod *v1.Pod, gracePeriodOverride int64, evictMsg string, annotations map[string]string) bool {
  499. // If the pod is marked as critical and static, and support for critical pod annotations is enabled,
  500. // do not evict such pods. Static pods are not re-admitted after evictions.
  501. // https://github.com/kubernetes/kubernetes/issues/40573 has more details.
  502. if kubelettypes.IsCriticalPod(pod) {
  503. klog.Errorf("eviction manager: cannot evict a critical pod %s", format.Pod(pod))
  504. return false
  505. }
  506. status := v1.PodStatus{
  507. Phase: v1.PodFailed,
  508. Message: evictMsg,
  509. Reason: Reason,
  510. }
  511. // record that we are evicting the pod
  512. m.recorder.AnnotatedEventf(pod, annotations, v1.EventTypeWarning, Reason, evictMsg)
  513. // this is a blocking call and should only return when the pod and its containers are killed.
  514. err := m.killPodFunc(pod, status, &gracePeriodOverride)
  515. if err != nil {
  516. klog.Errorf("eviction manager: pod %s failed to evict %v", format.Pod(pod), err)
  517. } else {
  518. klog.Infof("eviction manager: pod %s is evicted successfully", format.Pod(pod))
  519. }
  520. return true
  521. }