diff --git a/cmd/kubelet/app/server.go b/cmd/kubelet/app/server.go index 2cfe30cd3c5..db938ffd6a3 100644 --- a/cmd/kubelet/app/server.go +++ b/cmd/kubelet/app/server.go @@ -187,7 +187,7 @@ func UnsecuredKubeletConfig(s *options.KubeletServer) (*KubeletConfig, error) { return nil, err } - thresholds, err := eviction.ParseThresholdConfig(s.EvictionHard, s.EvictionSoft, s.EvictionSoftGracePeriod) + thresholds, err := eviction.ParseThresholdConfig(s.EvictionHard, s.EvictionSoft, s.EvictionSoftGracePeriod, s.EvictionMinimumReclaim) if err != nil { return nil, err } diff --git a/pkg/kubelet/eviction/helpers.go b/pkg/kubelet/eviction/helpers.go index e36cd8e1050..b9d5a4f196b 100644 --- a/pkg/kubelet/eviction/helpers.go +++ b/pkg/kubelet/eviction/helpers.go @@ -65,7 +65,7 @@ func validSignal(signal Signal) bool { } // ParseThresholdConfig parses the flags for thresholds. -func ParseThresholdConfig(evictionHard, evictionSoft, evictionSoftGracePeriod string) ([]Threshold, error) { +func ParseThresholdConfig(evictionHard, evictionSoft, evictionSoftGracePeriod, evictionMinimumReclaim string) ([]Threshold, error) { results := []Threshold{} hardThresholds, err := parseThresholdStatements(evictionHard) @@ -82,6 +82,10 @@ func ParseThresholdConfig(evictionHard, evictionSoft, evictionSoftGracePeriod st if err != nil { return nil, err } + minReclaims, err := parseMinimumReclaims(evictionMinimumReclaim) + if err != nil { + return nil, err + } for i := range softThresholds { signal := softThresholds[i].Signal period, found := gracePeriods[signal] @@ -91,6 +95,14 @@ func ParseThresholdConfig(evictionHard, evictionSoft, evictionSoftGracePeriod st softThresholds[i].GracePeriod = period } results = append(results, softThresholds...) + for i := range results { + for signal, minReclaim := range minReclaims { + if results[i].Signal == signal { + results[i].MinReclaim = &minReclaim + break + } + } + } return results, nil } @@ -186,6 +198,38 @@ func parseGracePeriods(expr string) (map[Signal]time.Duration, error) { return results, nil } +// parseMinimumReclaims parses the minimum reclaim statements +func parseMinimumReclaims(expr string) (map[Signal]resource.Quantity, error) { + if len(expr) == 0 { + return nil, nil + } + results := map[Signal]resource.Quantity{} + statements := strings.Split(expr, ",") + for _, statement := range statements { + parts := strings.Split(statement, "=") + if len(parts) != 2 { + return nil, fmt.Errorf("invalid eviction minimum reclaim syntax: %v, expected =", statement) + } + signal := Signal(parts[0]) + if !validSignal(signal) { + return nil, fmt.Errorf(unsupportedEvictionSignal, signal) + } + // check against duplicate statements + if _, found := results[signal]; found { + return nil, fmt.Errorf("duplicate eviction minimum reclaim specified for %v", signal) + } + quantity, err := resource.ParseQuantity(parts[1]) + if quantity.Sign() < 0 { + return nil, fmt.Errorf("negative eviction minimum reclaim specified for %v", signal) + } + if err != nil { + return nil, err + } + results[signal] = quantity + } + return results, nil +} + // diskUsage converts used bytes into a resource quantity. func diskUsage(fsStats *statsapi.FsStats) *resource.Quantity { if fsStats == nil || fsStats.UsedBytes == nil { diff --git a/pkg/kubelet/eviction/helpers_test.go b/pkg/kubelet/eviction/helpers_test.go index 98b767fa570..121fae5c8fc 100644 --- a/pkg/kubelet/eviction/helpers_test.go +++ b/pkg/kubelet/eviction/helpers_test.go @@ -41,6 +41,7 @@ func TestParseThresholdConfig(t *testing.T) { evictionHard string evictionSoft string evictionSoftGracePeriod string + evictionMinReclaim string expectErr bool expectThresholds []Threshold }{ @@ -48,6 +49,7 @@ func TestParseThresholdConfig(t *testing.T) { evictionHard: "", evictionSoft: "", evictionSoftGracePeriod: "", + evictionMinReclaim: "", expectErr: false, expectThresholds: []Threshold{}, }, @@ -55,18 +57,21 @@ func TestParseThresholdConfig(t *testing.T) { evictionHard: "memory.available<150Mi", evictionSoft: "memory.available<300Mi", evictionSoftGracePeriod: "memory.available=30s", + evictionMinReclaim: "memory.available=0", expectErr: false, expectThresholds: []Threshold{ { - Signal: SignalMemoryAvailable, - Operator: OpLessThan, - Value: quantityMustParse("150Mi"), + Signal: SignalMemoryAvailable, + Operator: OpLessThan, + Value: quantityMustParse("150Mi"), + MinReclaim: quantityMustParse("0"), }, { Signal: SignalMemoryAvailable, Operator: OpLessThan, Value: quantityMustParse("300Mi"), GracePeriod: gracePeriod, + MinReclaim: quantityMustParse("0"), }, }, }, @@ -74,6 +79,7 @@ func TestParseThresholdConfig(t *testing.T) { evictionHard: "mem.available<150Mi", evictionSoft: "", evictionSoftGracePeriod: "", + evictionMinReclaim: "", expectErr: true, expectThresholds: []Threshold{}, }, @@ -81,6 +87,7 @@ func TestParseThresholdConfig(t *testing.T) { evictionHard: "memory.available<150Mi,memory.available<100Mi", evictionSoft: "", evictionSoftGracePeriod: "", + evictionMinReclaim: "", expectErr: true, expectThresholds: []Threshold{}, }, @@ -88,6 +95,7 @@ func TestParseThresholdConfig(t *testing.T) { evictionHard: "memory.available<150Mi,invalid.foo<150Mi", evictionSoft: "", evictionSoftGracePeriod: "", + evictionMinReclaim: "", expectErr: true, expectThresholds: []Threshold{}, }, @@ -95,6 +103,7 @@ func TestParseThresholdConfig(t *testing.T) { evictionHard: "", evictionSoft: "memory.available<150Mi", evictionSoftGracePeriod: "", + evictionMinReclaim: "", expectErr: true, expectThresholds: []Threshold{}, }, @@ -105,9 +114,25 @@ func TestParseThresholdConfig(t *testing.T) { expectErr: true, expectThresholds: []Threshold{}, }, + "neg-reclaim": { + evictionHard: "", + evictionSoft: "", + evictionSoftGracePeriod: "", + evictionMinReclaim: "memory.available=-300Mi", + expectErr: true, + expectThresholds: []Threshold{}, + }, + "duplicate-reclaim": { + evictionHard: "", + evictionSoft: "", + evictionSoftGracePeriod: "", + evictionMinReclaim: "memory.available=-300Mi,memory.available=-100Mi", + expectErr: true, + expectThresholds: []Threshold{}, + }, } for testName, testCase := range testCases { - thresholds, err := ParseThresholdConfig(testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod) + thresholds, err := ParseThresholdConfig(testCase.evictionHard, testCase.evictionSoft, testCase.evictionSoftGracePeriod, testCase.evictionMinReclaim) if testCase.expectErr != (err != nil) { t.Errorf("Err not as expected, test: %v, error expected: %v, actual: %v", testName, testCase.expectErr, err) } @@ -150,7 +175,8 @@ func thresholdEqual(a Threshold, b Threshold) bool { return a.GracePeriod == b.GracePeriod && a.Operator == b.Operator && a.Signal == b.Signal && - a.Value.Cmp(*b.Value) == 0 + a.Value.Cmp(*b.Value) == 0 && + a.MinReclaim.Cmp(*b.MinReclaim) == 0 } // TestOrderedByQoS ensures we order BestEffort < Burstable < Guaranteed diff --git a/pkg/kubelet/eviction/types.go b/pkg/kubelet/eviction/types.go index 185860722af..54173f871a9 100644 --- a/pkg/kubelet/eviction/types.go +++ b/pkg/kubelet/eviction/types.go @@ -60,6 +60,8 @@ type Threshold struct { Value *resource.Quantity // GracePeriod represents the amount of time that a threshold must be met before eviction is triggered. GracePeriod time.Duration + // MinReclaim represents the minimum amount of resource to reclaim if the threshold is met. + MinReclaim *resource.Quantity } // Manager evaluates when an eviction threshold for node stability has been met on the node.