From 7d8d9df0247cdd70b01e61a108fcbc17e1192d39 Mon Sep 17 00:00:00 2001 From: Daniel Reuter Date: Mon, 4 Nov 2024 14:08:53 +0100 Subject: [PATCH] feat: make the required replicas configurable --- README_CHECKS.md | 4 ++-- cmd/kube-score/main.go | 4 ++++ config/config.go | 2 ++ score/deployment/deployment.go | 11 ++++++----- score/deployment_test.go | 5 ++++- score/hpa/hpa.go | 11 ++++++----- score/hpa_test.go | 6 +++++- score/score.go | 4 ++-- 8 files changed, 31 insertions(+), 16 deletions(-) diff --git a/README_CHECKS.md b/README_CHECKS.md index 9fed577..d82f158 100644 --- a/README_CHECKS.md +++ b/README_CHECKS.md @@ -2,7 +2,7 @@ | ID | Target | Description | Enabled | |----|--------|-------------|---------| | deployment-strategy | Deployment | Makes sure that all Deployments targeted by service use RollingUpdate strategy | default | -| deployment-replicas | Deployment | Makes sure that Deployment has multiple replicas | default | +| deployment-replicas | Deployment | Makes sure that Deployment has multiple replicas. The --min-replicas-deployment flag can be used to specify the required minimum. Default is 2. | default | | ingress-targets-service | Ingress | Makes sure that the Ingress targets a Service | default | | cronjob-has-deadline | CronJob | Makes sure that all CronJobs has a configured deadline | default | | cronjob-restartpolicy | CronJob | Makes sure CronJobs have a valid RestartPolicy | default | @@ -37,5 +37,5 @@ | statefulset-pod-selector-labels-match-template-metadata-labels | StatefulSet | Ensure the StatefulSet selector labels match the template metadata labels. | default | | label-values | all | Validates label values | default | | horizontalpodautoscaler-has-target | HorizontalPodAutoscaler | Makes sure that the HPA targets a valid object | default | -| horizontalpodautoscaler-replicas | HorizontalPodAutoscaler | Makes sure that the HPA at least 2 replicas | default | +| horizontalpodautoscaler-replicas | HorizontalPodAutoscaler | Makes sure that the HPA has multiple replicas. The --min-replicas-hpa flag can be used to specify the required minimum. Default is 2. | default | | pod-topology-spread-constraints | Pod | Pod Topology Spread Constraints | default | diff --git a/cmd/kube-score/main.go b/cmd/kube-score/main.go index 7fbc900..deb1d0f 100644 --- a/cmd/kube-score/main.go +++ b/cmd/kube-score/main.go @@ -114,6 +114,8 @@ func scoreFiles(binName string, args []string) error { disableOptionalChecksAnnotation := fs.Bool("disable-optional-checks-annotations", false, "Set to true to disable the effect of the 'kube-score/enable' annotations") allDefaultOptional := fs.Bool("all-default-optional", false, "Set to true to enable all tests") kubernetesVersion := fs.String("kubernetes-version", "v1.18", "Setting the kubernetes-version will affect the checks ran against the manifests. Set this to the version of Kubernetes that you're using in production for the best results.") + minReplicasDeployment := fs.Int("min-replicas-deployment", 2, "Minimum required number of replicas for a deployment") + minReplicasHPA := fs.Int("min-replicas-hpa", 2, "Minimum required number of replicas for a horizontal pod autoscaler") setDefault(fs, binName, "score", false) err := fs.Parse(args) @@ -199,6 +201,8 @@ Use "-" as filename to read from STDIN.`, execName(binName)) UseIgnoreChecksAnnotation: !*disableIgnoreChecksAnnotation, UseOptionalChecksAnnotation: !*disableOptionalChecksAnnotation, KubernetesVersion: kubeVer, + MinReplicasDeployment: *minReplicasDeployment, + MinReplicasHPA: *minReplicasHPA, } p, err := parser.New(&parser.Config{ diff --git a/config/config.go b/config/config.go index e4c8953..8295d4c 100644 --- a/config/config.go +++ b/config/config.go @@ -14,6 +14,8 @@ type RunConfiguration struct { UseIgnoreChecksAnnotation bool UseOptionalChecksAnnotation bool KubernetesVersion Semver + MinReplicasDeployment int + MinReplicasHPA int } type Semver struct { diff --git a/score/deployment/deployment.go b/score/deployment/deployment.go index a1f2f9d..c8ea4c7 100644 --- a/score/deployment/deployment.go +++ b/score/deployment/deployment.go @@ -1,6 +1,7 @@ package deployment import ( + "fmt" ks "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/score/checks" "github.com/zegl/kube-score/score/internal" @@ -10,9 +11,9 @@ import ( "k8s.io/utils/ptr" ) -func Register(allChecks *checks.Checks, all ks.AllTypes) { +func Register(allChecks *checks.Checks, all ks.AllTypes, minReplicas int) { allChecks.RegisterDeploymentCheck("Deployment Strategy", `Makes sure that all Deployments targeted by service use RollingUpdate strategy`, deploymentRolloutStrategy(all.Services())) - allChecks.RegisterDeploymentCheck("Deployment Replicas", `Makes sure that Deployment has multiple replicas`, deploymentReplicas(all.Services(), all.HorizontalPodAutoscalers())) + allChecks.RegisterDeploymentCheck("Deployment Replicas", `Makes sure that Deployment has multiple replicas`, deploymentReplicas(all.Services(), all.HorizontalPodAutoscalers(), minReplicas)) } // deploymentRolloutStrategy checks if a Deployment has the update strategy on RollingUpdate if targeted by a service @@ -52,7 +53,7 @@ func deploymentRolloutStrategy(svcs []ks.Service) func(deployment v1.Deployment) } // deploymentReplicas checks if a Deployment has >= 2 replicas if not (targeted by service || has HorizontalPodAutoscaler) -func deploymentReplicas(svcs []ks.Service, hpas []ks.HpaTargeter) func(deployment v1.Deployment) (scorecard.TestScore, error) { +func deploymentReplicas(svcs []ks.Service, hpas []ks.HpaTargeter, minReplicas int) func(deployment v1.Deployment) (scorecard.TestScore, error) { svcsInNamespace := make(map[string][]map[string]string) for _, s := range svcs { svc := s.Service() @@ -98,11 +99,11 @@ func deploymentReplicas(svcs []ks.Service, hpas []ks.HpaTargeter) func(deploymen score.Skipped = true score.AddComment("", "Skipped as the Deployment is controlled by a HorizontalPodAutoscaler", "") } else { - if ptr.Deref(deployment.Spec.Replicas, 1) >= 2 { + if ptr.Deref(deployment.Spec.Replicas, 1) >= int32(minReplicas) { score.Grade = scorecard.GradeAllOK } else { score.Grade = scorecard.GradeWarning - score.AddComment("", "Deployment few replicas", "Deployments targeted by Services are recommended to have at least 2 replicas to prevent unwanted downtime.") + score.AddComment("", "Deployment few replicas", fmt.Sprintf("Deployments targeted by Services are recommended to have at least %d replicas to prevent unwanted downtime.", minReplicas)) } } diff --git a/score/deployment_test.go b/score/deployment_test.go index 13b3dab..e2802e3 100644 --- a/score/deployment_test.go +++ b/score/deployment_test.go @@ -1,6 +1,7 @@ package score import ( + "github.com/zegl/kube-score/config" "testing" "github.com/stretchr/testify/assert" @@ -49,7 +50,9 @@ func TestServiceNotTargetsDeploymentReplicasNotRelevant(t *testing.T) { func TestServiceTargetsDeploymentReplicasNok(t *testing.T) { t.Parallel() - testExpectedScore(t, "service-target-deployment-replica-1.yaml", "Deployment Replicas", scorecard.GradeWarning) + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("service-target-deployment-replica-1.yaml")}, nil, &config.RunConfiguration{ + MinReplicasDeployment: 2, + }, "Deployment Replicas", scorecard.GradeWarning) } func TestHPATargetsDeployment(t *testing.T) { diff --git a/score/hpa/hpa.go b/score/hpa/hpa.go index 63e5637..a972259 100644 --- a/score/hpa/hpa.go +++ b/score/hpa/hpa.go @@ -1,15 +1,16 @@ package hpa import ( + "fmt" "github.com/zegl/kube-score/domain" "github.com/zegl/kube-score/score/checks" "github.com/zegl/kube-score/scorecard" "k8s.io/utils/ptr" ) -func Register(allChecks *checks.Checks, allTargetableObjs []domain.BothMeta) { +func Register(allChecks *checks.Checks, allTargetableObjs []domain.BothMeta, minReplicas int) { allChecks.RegisterHorizontalPodAutoscalerCheck("HorizontalPodAutoscaler has target", `Makes sure that the HPA targets a valid object`, hpaHasTarget(allTargetableObjs)) - allChecks.RegisterHorizontalPodAutoscalerCheck("HorizontalPodAutoscaler Replicas", `Makes sure that the HPA has multiple replicas`, hpaHasMultipleReplicas()) + allChecks.RegisterHorizontalPodAutoscalerCheck("HorizontalPodAutoscaler Replicas", `Makes sure that the HPA has multiple replicas`, hpaHasMultipleReplicas(minReplicas)) } func hpaHasTarget(allTargetableObjs []domain.BothMeta) func(hpa domain.HpaTargeter) (scorecard.TestScore, error) { @@ -36,13 +37,13 @@ func hpaHasTarget(allTargetableObjs []domain.BothMeta) func(hpa domain.HpaTarget } } -func hpaHasMultipleReplicas() func(hpa domain.HpaTargeter) (scorecard.TestScore, error) { +func hpaHasMultipleReplicas(minReplicas int) func(hpa domain.HpaTargeter) (scorecard.TestScore, error) { return func(hpa domain.HpaTargeter) (score scorecard.TestScore, err error) { - if ptr.Deref(hpa.MinReplicas(), 1) >= 2 { + if ptr.Deref(hpa.MinReplicas(), 1) >= int32(minReplicas) { score.Grade = scorecard.GradeAllOK } else { score.Grade = scorecard.GradeWarning - score.AddComment("", "HPA few replicas", "HorizontalPodAutoscalers are recommended to have at least 2 replicas to prevent unwanted downtime.") + score.AddComment("", "HPA few replicas", fmt.Sprintf("HorizontalPodAutoscalers are recommended to have at least %d replicas to prevent unwanted downtime.", minReplicas)) } return } diff --git a/score/hpa_test.go b/score/hpa_test.go index 30d47f7..377211a 100644 --- a/score/hpa_test.go +++ b/score/hpa_test.go @@ -1,6 +1,8 @@ package score import ( + "github.com/zegl/kube-score/config" + ks "github.com/zegl/kube-score/domain" "testing" "github.com/zegl/kube-score/scorecard" @@ -28,5 +30,7 @@ func TestHorizontalPodAutoscalerMinReplicasOk(t *testing.T) { func TestHorizontalPodAutoscalerMinReplicasNok(t *testing.T) { t.Parallel() - testExpectedScore(t, "hpa-min-replicas-nok.yaml", "HorizontalPodAutoscaler Replicas", scorecard.GradeWarning) + testExpectedScoreWithConfig(t, []ks.NamedReader{testFile("hpa-min-replicas-nok.yaml")}, nil, &config.RunConfiguration{ + MinReplicasHPA: 2, + }, "HorizontalPodAutoscaler Replicas", scorecard.GradeWarning) } diff --git a/score/score.go b/score/score.go index df8a06e..f397a1a 100644 --- a/score/score.go +++ b/score/score.go @@ -32,7 +32,7 @@ func RegisterAllChecks(allObjects ks.AllTypes, checksConfig *checks.Config, runC runConfig = &config.RunConfiguration{} } - deployment.Register(allChecks, allObjects) + deployment.Register(allChecks, allObjects, runConfig.MinReplicasDeployment) ingress.Register(allChecks, allObjects) cronjob.Register(allChecks) container.Register(allChecks, runConfig.IgnoreContainerCpuLimitRequirement, runConfig.IgnoreContainerMemoryLimitRequirement) @@ -44,7 +44,7 @@ func RegisterAllChecks(allObjects ks.AllTypes, checksConfig *checks.Config, runC stable.Register(runConfig.KubernetesVersion, allChecks) apps.Register(allChecks, allObjects.HorizontalPodAutoscalers(), allObjects.Services()) meta.Register(allChecks) - hpa.Register(allChecks, allObjects.Metas()) + hpa.Register(allChecks, allObjects.Metas(), runConfig.MinReplicasHPA) podtopologyspreadconstraints.Register(allChecks) return allChecks