diff --git a/pkg/console/subresource/deployment/deployment.go b/pkg/console/subresource/deployment/deployment.go index a9d6491e4..32ac04687 100644 --- a/pkg/console/subresource/deployment/deployment.go +++ b/pkg/console/subresource/deployment/deployment.go @@ -184,13 +184,32 @@ func withAffinity( func withStrategy(deployment *appsv1.Deployment, infrastructureConfig *configv1.Infrastructure) { rollingUpdateParams := &appsv1.RollingUpdateDeployment{} if ShouldDeployHA(infrastructureConfig) { - rollingUpdateParams = &appsv1.RollingUpdateDeployment{ - MaxSurge: &intstr.IntOrString{ - IntVal: int32(3), - }, - MaxUnavailable: &intstr.IntOrString{ - IntVal: int32(1), - }, + topology := infrastructureConfig.Status.ControlPlaneTopology + if topology == configv1.DualReplicaTopologyMode || topology == configv1.HighlyAvailableArbiterMode { + // On 2-node topologies the required pod anti-affinity prevents + // scheduling a surge pod when every eligible node already runs + // a console pod, so maxUnavailable must be >= 1 to allow the + // rollout to make progress. + rollingUpdateParams = &appsv1.RollingUpdateDeployment{ + MaxSurge: &intstr.IntOrString{ + IntVal: int32(1), + }, + MaxUnavailable: &intstr.IntOrString{ + IntVal: int32(1), + }, + } + } else { + // On 3+ node topologies a free node is available for the surge + // pod, so maxUnavailable=0 guarantees zero-downtime rollouts: + // no old pod is terminated until its replacement is ready. + rollingUpdateParams = &appsv1.RollingUpdateDeployment{ + MaxSurge: &intstr.IntOrString{ + IntVal: int32(1), + }, + MaxUnavailable: &intstr.IntOrString{ + IntVal: int32(0), + }, + } } } deployment.Spec.Strategy.RollingUpdate = rollingUpdateParams diff --git a/pkg/console/subresource/deployment/deployment_test.go b/pkg/console/subresource/deployment/deployment_test.go index afd28d53f..444fdbfdc 100644 --- a/pkg/console/subresource/deployment/deployment_test.go +++ b/pkg/console/subresource/deployment/deployment_test.go @@ -264,10 +264,10 @@ func TestDefaultDeployment(t *testing.T) { Type: appsv1.RollingUpdateDeploymentStrategyType, RollingUpdate: &appsv1.RollingUpdateDeployment{ MaxSurge: &intstr.IntOrString{ - IntVal: int32(3), + IntVal: int32(1), }, MaxUnavailable: &intstr.IntOrString{ - IntVal: int32(1), + IntVal: int32(0), }, }, }, @@ -343,10 +343,10 @@ func TestDefaultDeployment(t *testing.T) { Type: appsv1.RollingUpdateDeploymentStrategyType, RollingUpdate: &appsv1.RollingUpdateDeployment{ MaxSurge: &intstr.IntOrString{ - IntVal: int32(3), + IntVal: int32(1), }, MaxUnavailable: &intstr.IntOrString{ - IntVal: int32(1), + IntVal: int32(0), }, }, }, @@ -494,10 +494,10 @@ func TestDefaultDeployment(t *testing.T) { Type: appsv1.RollingUpdateDeploymentStrategyType, RollingUpdate: &appsv1.RollingUpdateDeployment{ MaxSurge: &intstr.IntOrString{ - IntVal: int32(3), + IntVal: int32(1), }, MaxUnavailable: &intstr.IntOrString{ - IntVal: int32(1), + IntVal: int32(0), }, }, }, @@ -1518,11 +1518,21 @@ func TestWithStrategy(t *testing.T) { infrastructureConfigSingleReplica := infrastructureConfigWithTopology(configv1.SingleReplicaTopologyMode, configv1.SingleReplicaTopologyMode) infrastructureConfigExternalTopologyHighlyAvailable := infrastructureConfigWithTopology(configv1.ExternalTopologyMode, configv1.HighlyAvailableTopologyMode) infrastructureConfigExternalTopologySingleReplica := infrastructureConfigWithTopology(configv1.ExternalTopologyMode, configv1.SingleReplicaTopologyMode) + infrastructureConfigDualReplica := infrastructureConfigWithTopology(configv1.DualReplicaTopologyMode, configv1.HighlyAvailableTopologyMode) + infrastructureConfigArbiter := infrastructureConfigWithTopology(configv1.HighlyAvailableArbiterMode, configv1.HighlyAvailableTopologyMode) singleReplicaStrategy := appsv1.RollingUpdateDeployment{} - highAvailStrategy := appsv1.RollingUpdateDeployment{ + zeroDowntimeStrategy := appsv1.RollingUpdateDeployment{ MaxSurge: &intstr.IntOrString{ - IntVal: int32(3), + IntVal: int32(1), + }, + MaxUnavailable: &intstr.IntOrString{ + IntVal: int32(0), + }, + } + constrainedHAStrategy := appsv1.RollingUpdateDeployment{ + MaxSurge: &intstr.IntOrString{ + IntVal: int32(1), }, MaxUnavailable: &intstr.IntOrString{ IntVal: int32(1), @@ -1557,7 +1567,7 @@ func TestWithStrategy(t *testing.T) { want: &appsv1.Deployment{ Spec: appsv1.DeploymentSpec{ Strategy: appsv1.DeploymentStrategy{ - RollingUpdate: &highAvailStrategy, + RollingUpdate: &zeroDowntimeStrategy, }, }, }, @@ -1585,7 +1595,35 @@ func TestWithStrategy(t *testing.T) { want: &appsv1.Deployment{ Spec: appsv1.DeploymentSpec{ Strategy: appsv1.DeploymentStrategy{ - RollingUpdate: &highAvailStrategy, + RollingUpdate: &zeroDowntimeStrategy, + }, + }, + }, + }, + { + name: "Test DualReplica Strategy uses maxUnavailable=1", + args: args{ + deployment: &appsv1.Deployment{}, + infrastructureConfig: infrastructureConfigDualReplica, + }, + want: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Strategy: appsv1.DeploymentStrategy{ + RollingUpdate: &constrainedHAStrategy, + }, + }, + }, + }, + { + name: "Test Arbiter Strategy uses maxUnavailable=1", + args: args{ + deployment: &appsv1.Deployment{}, + infrastructureConfig: infrastructureConfigArbiter, + }, + want: &appsv1.Deployment{ + Spec: appsv1.DeploymentSpec{ + Strategy: appsv1.DeploymentStrategy{ + RollingUpdate: &constrainedHAStrategy, }, }, }, @@ -1891,10 +1929,10 @@ func TestDefaultDownloadsDeployment(t *testing.T) { Type: appsv1.RollingUpdateDeploymentStrategyType, RollingUpdate: &appsv1.RollingUpdateDeployment{ MaxSurge: &intstr.IntOrString{ - IntVal: int32(3), + IntVal: int32(1), }, MaxUnavailable: &intstr.IntOrString{ - IntVal: int32(1), + IntVal: int32(0), }, }, },