eviction.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. // Copyright 2019 Yunion
  2. //
  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. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package eviction
  15. import (
  16. "encoding/json"
  17. "strconv"
  18. "strings"
  19. "k8s.io/apimachinery/pkg/api/resource"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. )
  24. // Signal defines a signal that can trigger eviction of pods on a node.
  25. type Signal string
  26. const (
  27. // SignalMemoryAvailable is memory available (i.e. capacity - workingSet), in bytes.
  28. SignalMemoryAvailable Signal = "memory.available"
  29. // SignalNodeFsAvailable is amount of storage available on filesystem that kubelet uses for volumes, daemon logs, etc.
  30. SignalNodeFsAvailable Signal = "nodefs.available"
  31. // SignalNodeFsInodesFree is amount of storage available on filesystem that container runtime uses for storing images and container writable layers.
  32. SignalNodeFsInodesFree Signal = "nodefs.inodesFree"
  33. // SignalImageFsAvailable is amount of storage available on filesystem that container runtime uses for storing images and container writable layers.
  34. SignalImageFsAvailable Signal = "imagefs.available"
  35. // SignalImageFsInodesFree is amount of inodes available on filesystem that container runtime uses for storing images and container writable layers.
  36. SignalImageFsInodesFree Signal = "imagefs.inodesFree"
  37. // SignalAllocatableMemoryAvailable is amount of memory available for pod allocation (i.e. allocatable - workingSet (of pods), in bytes)
  38. // SignalAllocatableMemoryAvailable Signal = "allocatableMemory.available"
  39. // SignalPIDAvailable is amount of PID available for pod allocation
  40. SignalPIDAvailable Signal = "pid.available"
  41. )
  42. var (
  43. // DefaultEvictionHard includes default options for kubelet hard eviction
  44. // ref: https://github.com/kubernetes/kubernetes/blob/ec39cc2eafffa51b3267e3bd64fbd2598c0db94d/pkg/kubelet/apis/config/v1beta1/defaults_linux.go#L21
  45. DefaultEvictionHard = map[string]string{
  46. string(SignalMemoryAvailable): "100Mi",
  47. string(SignalNodeFsAvailable): "10%",
  48. string(SignalNodeFsInodesFree): "5%",
  49. string(SignalImageFsAvailable): "15%",
  50. }
  51. )
  52. type ThresholdMap map[Signal]*Threshold
  53. func (m ThresholdMap) GetMemoryAvailable() *Threshold {
  54. return m[SignalMemoryAvailable]
  55. }
  56. func (m ThresholdMap) GetNodeFsAvailable() *Threshold {
  57. return m[SignalNodeFsAvailable]
  58. }
  59. func (m ThresholdMap) GetNodeFsInodesFree() *Threshold {
  60. return m[SignalNodeFsInodesFree]
  61. }
  62. func (m ThresholdMap) GetImageFsAvailable() *Threshold {
  63. return m[SignalImageFsAvailable]
  64. }
  65. // Config holds information about how eviction is configured.
  66. type Config interface {
  67. GetHard() ThresholdMap
  68. String() string
  69. }
  70. // config implements Config interface
  71. type config struct {
  72. // hard holds configuration of hardThresholds
  73. hard ThresholdMap
  74. }
  75. type configContent struct {
  76. // Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}
  77. EvictionHard map[string]string `json:"evictionHard"`
  78. // Map of signal names to quantities that defines soft eviction thresholds. For example: {"memory.available": "300Mi"}
  79. // EvictionSoft map[string]string `json:"evictionSoft"`
  80. }
  81. func NewConfig(yamlContent []byte) (Config, error) {
  82. obj, err := jsonutils.ParseYAML(string(yamlContent))
  83. if err != nil {
  84. return nil, errors.Wrapf(err, "Parse yaml content %q", yamlContent)
  85. }
  86. content := new(configContent)
  87. if err := obj.Unmarshal(content); err != nil {
  88. return nil, errors.Wrap(err, "Unmarshal eviction content")
  89. }
  90. hardThresholds, err := parseThresholdConfig(content.EvictionHard)
  91. if err != nil {
  92. return nil, errors.Wrap(err, "Parse hard thresholds")
  93. }
  94. return &config{
  95. hard: hardThresholds,
  96. }, nil
  97. }
  98. func (c *config) GetHard() ThresholdMap {
  99. return c.hard
  100. }
  101. func (c *config) String() string {
  102. out := map[string]interface{}{
  103. "evictionHard": c.hard,
  104. }
  105. bytes, err := json.Marshal(out)
  106. if err != nil {
  107. log.Errorf("Marshal eviction config error: %v", err)
  108. }
  109. return string(bytes)
  110. }
  111. func parseThresholdConfig(evictionHard map[string]string) (map[Signal]*Threshold, error) {
  112. results := map[Signal]*Threshold{}
  113. hardThresholds, err := parseHardThresholdStatements(evictionHard)
  114. if err != nil {
  115. return nil, errors.Wrap(err, "Parse hard threshold")
  116. }
  117. for _, r := range hardThresholds {
  118. results[r.Signal] = r
  119. }
  120. return results, nil
  121. }
  122. func parseHardThresholdStatements(statements map[string]string) ([]*Threshold, error) {
  123. results := []*Threshold{}
  124. for _, signal := range []Signal{
  125. SignalMemoryAvailable,
  126. SignalNodeFsAvailable,
  127. SignalNodeFsInodesFree,
  128. SignalImageFsAvailable,
  129. SignalImageFsInodesFree,
  130. SignalPIDAvailable,
  131. } {
  132. val, ok := statements[string(signal)]
  133. if !ok {
  134. // try get from default setting
  135. val, ok = DefaultEvictionHard[string(signal)]
  136. if !ok {
  137. continue
  138. }
  139. }
  140. result, err := parseThresholdStatement(signal, val)
  141. if err != nil {
  142. return nil, errors.Wrapf(err, "Parse signal %q with val %q", signal, val)
  143. }
  144. if result == nil {
  145. continue
  146. }
  147. results = append(results, result)
  148. }
  149. return results, nil
  150. }
  151. func parseThresholdStatement(signal Signal, val string) (*Threshold, error) {
  152. operator, ok := OpForSignal[signal]
  153. if !ok {
  154. return nil, errors.Errorf("Unsupported signal %q", signal)
  155. }
  156. if strings.HasSuffix(val, "%") {
  157. // ignore 0% and 100%
  158. if val == "0%" || val == "100%" {
  159. return nil, nil
  160. }
  161. percentage, err := parsePercentage(val)
  162. if err != nil {
  163. return nil, errors.Wrapf(err, "Parse val %q to percentage", val)
  164. }
  165. if percentage < 0 {
  166. return nil, errors.Errorf("Eviction percentage threshold %q must be >= 0%%: %q", signal, val)
  167. }
  168. if percentage > 100 {
  169. return nil, errors.Errorf("Eviction percentage threshold %q must be <= 100%%: %q", signal, val)
  170. }
  171. return &Threshold{
  172. Signal: signal,
  173. Operator: operator,
  174. Value: ThresholdValue{
  175. Percentage: percentage,
  176. },
  177. }, nil
  178. }
  179. quantity, err := resource.ParseQuantity(val)
  180. if err != nil {
  181. return nil, err
  182. }
  183. if quantity.Sign() < 0 || quantity.IsZero() {
  184. return nil, errors.Errorf("Eviction threshold %q must be positive: %s", signal, &quantity)
  185. }
  186. return &Threshold{
  187. Signal: signal,
  188. Operator: operator,
  189. Value: ThresholdValue{
  190. Quantity: &quantity,
  191. },
  192. }, nil
  193. }
  194. func parsePercentage(input string) (float32, error) {
  195. val, err := strconv.ParseFloat(strings.TrimRight(input, "%"), 32)
  196. if err != nil {
  197. return 0, err
  198. }
  199. return float32(val) / 100, nil
  200. }
  201. // ThresholdOperator is the operator used to express a Threshold.
  202. type ThresholdOperator string
  203. const (
  204. // OpLessThan is the operator that expresses a less than operator.
  205. OpLessThan ThresholdOperator = "LessThan"
  206. )
  207. // OpForSignal maps Signals to ThresholdOperators.
  208. // Today, the only supported operator is "LessThan".
  209. var OpForSignal = map[Signal]ThresholdOperator{
  210. SignalMemoryAvailable: OpLessThan,
  211. SignalNodeFsAvailable: OpLessThan,
  212. SignalNodeFsInodesFree: OpLessThan,
  213. SignalImageFsAvailable: OpLessThan,
  214. SignalImageFsInodesFree: OpLessThan,
  215. SignalPIDAvailable: OpLessThan,
  216. }
  217. // Threshold defines a metric for when eviction should occur.
  218. type Threshold struct {
  219. // Signal defines the entity that was measured.
  220. Signal Signal
  221. // Operator represents a relationship of a signal to a value.
  222. Operator ThresholdOperator
  223. // Value is the threshold the resource is evaluated against.
  224. Value ThresholdValue
  225. }
  226. // ThresholdValue is a value holder that abstracts literal versus percentage based quantity
  227. type ThresholdValue struct {
  228. // Quantity is a quantity associated with the signal
  229. Quantity *resource.Quantity
  230. // Percentage represents the usage percentage over the total resource
  231. Percentage float32
  232. }