Repository: zegl/kube-score
Pull request #429 (2022-02-03 17:41:50)

Feature #413: container ephemeral storage request and limit check
<!--
    Optional: Add this change to the release notes by adding a RELNOTE comment
    If this shouldn't appear in the notes, simply remove this.
-->

```
RELNOTE: Implement new Pod container resource checks for ephemeral storage. Default tests for request and limit settings. Optional test to ensure matching ephemeral storage request and limit values.
```



Patch:
diff --git a/score/container/container.go b/score/container/container.go
index 09d6d73..6d24db3 100644
--- a/score/container/container.go
+++ b/score/container/container.go
@@ -17,6 +17,8 @@ func Register(allChecks *checks.Checks, cnf config.Configuration) {
 	allChecks.RegisterOptionalPodCheck("Container Memory Requests Equal Limits", `Makes sure that all pods have the same memory requests as limits set.`, containerMemoryRequestsEqualLimits)
 	allChecks.RegisterPodCheck("Container Image Tag", `Makes sure that a explicit non-latest tag is used`, containerImageTag)
 	allChecks.RegisterPodCheck("Container Image Pull Policy", `Makes sure that the pullPolicy is set to Always. This makes sure that imagePullSecrets are always validated.`, containerImagePullPolicy)
+	allChecks.RegisterPodCheck("Container Ephemeral Storage Request and Limit", "Makes sure all pods have ephemeral-storage requests and limits set", containerStorageEphemeralRequestAndLimit)
+	allChecks.RegisterOptionalPodCheck("Container Ephemeral Storage Request Equals Limit", "Make sure all pods have matching ephemeral-storage requests and limits", containerStorageEphemeralRequestEqualsLimit)
 }
 
 // containerResources makes sure that the container has resource requests and limits set
@@ -202,3 +204,46 @@ func containerTag(image string) string {
 	}
 	return ""
 }
+
+func containerStorageEphemeralRequestAndLimit(podTemplate corev1.PodTemplateSpec, typeMeta metav1.TypeMeta) (score scorecard.TestScore) {
+
+	allContainers := podTemplate.Spec.InitContainers
+	allContainers = append(allContainers, podTemplate.Spec.Containers...)
+
+	score.Grade = scorecard.GradeAllOK
+
+	for _, container := range allContainers {
+		if container.Resources.Limits.StorageEphemeral().IsZero() {
+			score.AddComment(container.Name, "Ephemeral Storage limit is not set",
+				"Resource limits are recommended to avoid resource DDOS. Set resources.limits.ephemeral-storage")
+			score.Grade = scorecard.GradeCritical
+		} else if container.Resources.Requests.StorageEphemeral().IsZero() {
+			score.AddComment(container.Name, "Ephemeral Storage request is not set",
+				"Resource requests are recommended to make sure the application can start and run without crashing. Set resource.requests.ephemeral-storage")
+			score.Grade = scorecard.GradeWarning
+		}
+	}
+
+	return
+}
+
+func containerStorageEphemeralRequestEqualsLimit(podTemplate corev1.PodTemplateSpec, typeMeta metav1.TypeMeta) (score scorecard.TestScore) {
+
+	allContainers := podTemplate.Spec.InitContainers
+	allContainers = append(allContainers, podTemplate.Spec.Containers...)
+
+	score.Grade = scorecard.GradeAllOK
+
+	for _, container := range allContainers {
+		if !container.Resources.Limits.StorageEphemeral().IsZero() && !container.Resources.Requests.StorageEphemeral().IsZero() {
+			requests := &container.Resources.Requests
+			limits := &container.Resources.Limits
+			if !requests.StorageEphemeral().Equal(*limits.StorageEphemeral()) {
+				score.AddComment(container.Name, "Ephemeral Storage request does not match limit", "Having equal requests and limits is recommended to avoid node resource DDOS during spikes")
+				score.Grade = scorecard.GradeCritical
+			}
+		}
+	}
+
+	return
+}
diff --git a/score/score.go b/score/score.go
index 0ce5b83..2cf6dad 100644
--- a/score/score.go
+++ b/score/score.go
@@ -17,7 +17,6 @@ import (
 	"github.com/zegl/kube-score/score/service"
 	"github.com/zegl/kube-score/score/stable"
 	"github.com/zegl/kube-score/scorecard"
-
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )


Test patch:
diff --git a/score/score_test.go b/score/score_test.go
index 21fe0fa..3e45018 100644
--- a/score/score_test.go
+++ b/score/score_test.go
@@ -5,12 +5,11 @@ import (
 	"os"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/zegl/kube-score/config"
 	ks "github.com/zegl/kube-score/domain"
 	"github.com/zegl/kube-score/parser"
 	"github.com/zegl/kube-score/scorecard"
-
-	"github.com/stretchr/testify/assert"
 )
 
 func testFile(name string) *os.File {
@@ -307,3 +306,44 @@ func TestList(t *testing.T) {
 	assert.True(t, hasService)
 	assert.True(t, hasDeployment)
 }
+
+// Note the input file specifies a condition that would fail the optional matching request and limit test, but returns GradeAllOK
+// when only the default case is evaluated
+func TestPodContainerStorageEphemeralRequestAndLimitOK(t *testing.T) {
+	t.Parallel()
+	testExpectedScore(t, "pod-ephemeral-storage-request-nomatch-limit.yaml", "Container Ephemeral Storage Request and Limit", scorecard.GradeAllOK)
+}
+
+func TestPodContainerStorageEphemeralNoLimit(t *testing.T) {
+	t.Parallel()
+	testExpectedScore(t, "pod-ephemeral-storage-missing-limit.yaml", "Container Ephemeral Storage Request and Limit", scorecard.GradeCritical)
+}
+
+func TestPodContainerStorageEphemeralNoRequest(t *testing.T) {
+	t.Parallel()
+	testExpectedScore(t, "pod-ephemeral-storage-missing-request.yaml", "Container Ephemeral Storage Request and Limit", scorecard.GradeWarning)
+}
+
+func TestPodContainerStorageEphemeralRequestEqualsLimit(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-ephemeral-storage-request-equals-limit"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, config.Configuration{
+		AllFiles:             []ks.NamedReader{testFile("pod-ephemeral-storage-request-matches-limit.yaml")},
+		EnabledOptionalTests: structMap,
+	}, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeAllOK)
+}
+
+func TestPodContainerStorageEphemeralRequestNoMatchLimit(t *testing.T) {
+	t.Parallel()
+
+	structMap := make(map[string]struct{})
+	structMap["container-ephemeral-storage-request-equals-limit"] = struct{}{}
+
+	testExpectedScoreWithConfig(t, config.Configuration{
+		AllFiles:             []ks.NamedReader{testFile("pod-ephemeral-storage-request-nomatch-limit.yaml")},
+		EnabledOptionalTests: structMap,
+	}, "Container Ephemeral Storage Request Equals Limit", scorecard.GradeCritical)
+}
diff --git a/score/testdata/pod-ephemeral-storage-missing-limit.yaml b/score/testdata/pod-ephemeral-storage-missing-limit.yaml
new file mode 100644
index 0000000..4eae07b
--- /dev/null
+++ b/score/testdata/pod-ephemeral-storage-missing-limit.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:123
+    resources:
+      limits:
+        cpu: 200m
+        memory: 1Gi
+      requests:
+        cpu: 200m
+        memory: 1Gi
+        ephemeral-storage: 2Gi
diff --git a/score/testdata/pod-ephemeral-storage-missing-request.yaml b/score/testdata/pod-ephemeral-storage-missing-request.yaml
new file mode 100644
index 0000000..e8f7724
--- /dev/null
+++ b/score/testdata/pod-ephemeral-storage-missing-request.yaml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:123
+    resources:
+      limits:
+        cpu: 200m
+        memory: 1Gi
+        ephemeral-storage: 2Gi
+      requests:
+        cpu: 200m
+        memory: 1Gi
diff --git a/score/testdata/pod-ephemeral-storage-request-matches-limit.yaml b/score/testdata/pod-ephemeral-storage-request-matches-limit.yaml
new file mode 100644
index 0000000..719e300
--- /dev/null
+++ b/score/testdata/pod-ephemeral-storage-request-matches-limit.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:123
+    resources:
+      limits:
+        cpu: 200m
+        memory: 1Gi
+        ephemeral-storage: 2Gi
+      requests:
+        cpu: 200m
+        memory: 1Gi
+        ephemeral-storage: 2Gi
diff --git a/score/testdata/pod-ephemeral-storage-request-nomatch-limit.yaml b/score/testdata/pod-ephemeral-storage-request-nomatch-limit.yaml
new file mode 100644
index 0000000..f7ff1cc
--- /dev/null
+++ b/score/testdata/pod-ephemeral-storage-request-nomatch-limit.yaml
@@ -0,0 +1,17 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: pod-test-1
+spec:
+  containers:
+  - name: foobar
+    image: foo/bar:123
+    resources:
+      limits:
+        cpu: 200m
+        memory: 1Gi
+        ephemeral-storage: 2Gi
+      requests:
+        cpu: 200m
+        memory: 1Gi
+        ephemeral-storage: 1Gi