Ignore 0% and 100% eviction thresholds

Primarily, this gives a way to explicitly disable eviction, which is
necessary to use omitempty on EvictionHard.
See: https://github.com/kubernetes/kubernetes/pull/53833#discussion_r166672137

As justification for this approach, neither 0% nor 100% make sense as
eviction thresholds; in the "less-than" case, you can't have less than
0% of a resource and 100% perpetually evicts; in the
"greater-than" case (assuming we ever add a resource with this
semantic), the reasoning is the reverse (not more than 100%, 0%
perpetually evicts).
pull/6/head
Michael Taufen 2018-02-09 14:51:52 -08:00
parent 977d03bd8b
commit 21dbbe14f2
4 changed files with 41 additions and 16 deletions

View File

@ -212,10 +212,10 @@ type KubeletConfiguration struct {
SerializeImagePulls *bool `json:"serializeImagePulls"` SerializeImagePulls *bool `json:"serializeImagePulls"`
// Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}. // Map of signal names to quantities that defines hard eviction thresholds. For example: {"memory.available": "300Mi"}.
// +optional // +optional
EvictionHard map[string]string `json:"evictionHard"` EvictionHard map[string]string `json:"evictionHard,omitempty"`
// Map of signal names to quantities that defines soft eviction thresholds. For example: {"memory.available": "300Mi"}. // Map of signal names to quantities that defines soft eviction thresholds. For example: {"memory.available": "300Mi"}.
// +optional // +optional
EvictionSoft map[string]string `json:"evictionSoft"` EvictionSoft map[string]string `json:"evictionSoft,omitempty"`
// Map of signal names to quantities that defines grace periods for each soft eviction signal. For example: {"memory.available": "30s"}. // Map of signal names to quantities that defines grace periods for each soft eviction signal. For example: {"memory.available": "30s"}.
// +optional // +optional
EvictionSoftGracePeriod map[string]string `json:"evictionSoftGracePeriod"` EvictionSoftGracePeriod map[string]string `json:"evictionSoftGracePeriod"`

View File

@ -107,7 +107,6 @@ func ParseThresholdConfig(allocatableConfig []string, evictionHard, evictionSoft
return nil, err return nil, err
} }
results = append(results, hardThresholds...) results = append(results, hardThresholds...)
softThresholds, err := parseThresholdStatements(evictionSoft) softThresholds, err := parseThresholdStatements(evictionSoft)
if err != nil { if err != nil {
return nil, err return nil, err
@ -151,26 +150,36 @@ func parseThresholdStatements(statements map[string]string) ([]evictionapi.Thres
if err != nil { if err != nil {
return nil, err return nil, err
} }
results = append(results, result) if result != nil {
results = append(results, *result)
}
} }
return results, nil return results, nil
} }
// parseThresholdStatement parses a threshold statement. // parseThresholdStatement parses a threshold statement and returns a threshold,
func parseThresholdStatement(signal evictionapi.Signal, val string) (evictionapi.Threshold, error) { // or nil if the threshold should be ignored.
func parseThresholdStatement(signal evictionapi.Signal, val string) (*evictionapi.Threshold, error) {
if !validSignal(signal) { if !validSignal(signal) {
return evictionapi.Threshold{}, fmt.Errorf(unsupportedEvictionSignal, signal) return nil, fmt.Errorf(unsupportedEvictionSignal, signal)
} }
operator := evictionapi.OpForSignal[signal] operator := evictionapi.OpForSignal[signal]
if strings.HasSuffix(val, "%") { if strings.HasSuffix(val, "%") {
// ignore 0% and 100%
if val == "0%" || val == "100%" {
return nil, nil
}
percentage, err := parsePercentage(val) percentage, err := parsePercentage(val)
if err != nil { if err != nil {
return evictionapi.Threshold{}, err return nil, err
} }
if percentage <= 0 { if percentage < 0 {
return evictionapi.Threshold{}, fmt.Errorf("eviction percentage threshold %v must be positive: %s", signal, val) return nil, fmt.Errorf("eviction percentage threshold %v must be >= 0%%: %s", signal, val)
} }
return evictionapi.Threshold{ if percentage > 100 {
return nil, fmt.Errorf("eviction percentage threshold %v must be <= 100%%: %s", signal, val)
}
return &evictionapi.Threshold{
Signal: signal, Signal: signal,
Operator: operator, Operator: operator,
Value: evictionapi.ThresholdValue{ Value: evictionapi.ThresholdValue{
@ -180,12 +189,12 @@ func parseThresholdStatement(signal evictionapi.Signal, val string) (evictionapi
} }
quantity, err := resource.ParseQuantity(val) quantity, err := resource.ParseQuantity(val)
if err != nil { if err != nil {
return evictionapi.Threshold{}, err return nil, err
} }
if quantity.Sign() < 0 || quantity.IsZero() { if quantity.Sign() < 0 || quantity.IsZero() {
return evictionapi.Threshold{}, fmt.Errorf("eviction threshold %v must be positive: %s", signal, &quantity) return nil, fmt.Errorf("eviction threshold %v must be positive: %s", signal, &quantity)
} }
return evictionapi.Threshold{ return &evictionapi.Threshold{
Signal: signal, Signal: signal,
Operator: operator, Operator: operator,
Value: evictionapi.ThresholdValue{ Value: evictionapi.ThresholdValue{

View File

@ -288,6 +288,20 @@ func TestParseThresholdConfig(t *testing.T) {
}, },
}, },
}, },
"disable via 0%": {
allocatableConfig: []string{},
evictionHard: map[string]string{"memory.available": "0%"},
evictionSoft: map[string]string{"memory.available": "0%"},
expectErr: false,
expectThresholds: []evictionapi.Threshold{},
},
"disable via 100%": {
allocatableConfig: []string{},
evictionHard: map[string]string{"memory.available": "100%"},
evictionSoft: map[string]string{"memory.available": "100%"},
expectErr: false,
expectThresholds: []evictionapi.Threshold{},
},
"invalid-signal": { "invalid-signal": {
allocatableConfig: []string{}, allocatableConfig: []string{},
evictionHard: map[string]string{"mem.available": "150Mi"}, evictionHard: map[string]string{"mem.available": "150Mi"},

View File

@ -170,7 +170,8 @@ var _ = framework.KubeDescribe("LocalStorageSoftEviction [Slow] [Serial] [Disrup
initialConfig.EvictionMaxPodGracePeriod = 30 initialConfig.EvictionMaxPodGracePeriod = 30
initialConfig.EvictionMinimumReclaim = map[string]string{} initialConfig.EvictionMinimumReclaim = map[string]string{}
// Ensure that pods are not evicted because of the eviction-hard threshold // Ensure that pods are not evicted because of the eviction-hard threshold
initialConfig.EvictionHard = map[string]string{} // setting a threshold to 0% disables; non-empty map overrides default value (necessary due to omitempty)
initialConfig.EvictionHard = map[string]string{"memory.available": "0%"}
}) })
runEvictionTest(f, pressureTimeout, expectedNodeCondition, logDiskMetrics, []podEvictSpec{ runEvictionTest(f, pressureTimeout, expectedNodeCondition, logDiskMetrics, []podEvictSpec{
{ {
@ -192,7 +193,8 @@ var _ = framework.KubeDescribe("LocalStorageCapacityIsolationEviction [Slow] [Se
Context(fmt.Sprintf(testContextFmt, "evictions due to pod local storage violations"), func() { Context(fmt.Sprintf(testContextFmt, "evictions due to pod local storage violations"), func() {
tempSetCurrentKubeletConfig(f, func(initialConfig *kubeletconfig.KubeletConfiguration) { tempSetCurrentKubeletConfig(f, func(initialConfig *kubeletconfig.KubeletConfiguration) {
initialConfig.FeatureGates[string(features.LocalStorageCapacityIsolation)] = true initialConfig.FeatureGates[string(features.LocalStorageCapacityIsolation)] = true
initialConfig.EvictionHard = map[string]string{} // setting a threshold to 0% disables; non-empty map overrides default value (necessary due to omitempty)
initialConfig.EvictionHard = map[string]string{"memory.available": "0%"}
}) })
sizeLimit := resource.MustParse("100Mi") sizeLimit := resource.MustParse("100Mi")
useOverLimit := 101 /* Mb */ useOverLimit := 101 /* Mb */