Skip to content

Commit ed0d09e

Browse files
Merge pull request #789 from tjungblu/OCPBUGS-57444
OCPBUGS-57444: set appropriate rolling update settings
2 parents 8e30dc7 + 0f3451c commit ed0d09e

File tree

3 files changed

+124
-5
lines changed

3 files changed

+124
-5
lines changed

bindata/oauth-openshift/deployment.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ spec:
99
strategy:
1010
type: RollingUpdate
1111
rollingUpdate:
12-
maxUnavailable: 1
12+
# those are being adjusted for each control plane size by the deployment controller
13+
maxUnavailable: 0
1314
maxSurge: 0
1415
selector:
1516
matchLabels:

pkg/controllers/deployment/deployment_controller.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"k8s.io/apimachinery/pkg/api/errors"
1212
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1313
"k8s.io/apimachinery/pkg/labels"
14+
"k8s.io/apimachinery/pkg/util/intstr"
1415
"k8s.io/client-go/informers"
1516
coreinformers "k8s.io/client-go/informers/core/v1"
1617
"k8s.io/client-go/kubernetes"
@@ -253,12 +254,17 @@ func (c *oauthServerDeploymentSyncer) Sync(ctx context.Context, syncContext fact
253254
return nil, false, append(errs, fmt.Errorf("unable to ensure at most one pod per node: %v", err))
254255
}
255256

256-
// Set the replica count to the number of master nodes.
257-
masterNodeCount, err := c.countNodes(expectedDeployment.Spec.Template.Spec.NodeSelector)
257+
// Set the replica count to the number of control plane nodes.
258+
controlPlaneCount, err := c.countNodes(expectedDeployment.Spec.Template.Spec.NodeSelector)
258259
if err != nil {
259-
return nil, false, append(errs, fmt.Errorf("failed to determine number of master nodes: %v", err))
260+
return nil, false, append(errs, fmt.Errorf("failed to determine number of control plane nodes: %v", err))
260261
}
261-
expectedDeployment.Spec.Replicas = masterNodeCount
262+
if controlPlaneCount == nil {
263+
return nil, false, append(errs, fmt.Errorf("found nil control plane nodes count"))
264+
}
265+
266+
expectedDeployment.Spec.Replicas = controlPlaneCount
267+
setRollingUpdateParameters(*controlPlaneCount, expectedDeployment)
262268

263269
deployment, _, err := resourceapply.ApplyDeployment(ctx, c.deployments,
264270
syncContext.Recorder(),
@@ -311,3 +317,14 @@ func (c *oauthServerDeploymentSyncer) getConfigResourceVersions() ([]string, err
311317

312318
return configRVs, nil
313319
}
320+
321+
// Given the control plane sizes, we adjust the max unavailable and max surge values to mimic "MinAvailable".
322+
// We always ensure it is controlPlaneCount - 1, as this allows us to keep have at least a single replica running.
323+
// We also set MaxSurge to always be exactly the control plane count, as this allows us to more quickly replace failing
324+
// deployments with a new replica set. This does not clash with the pod anti affinity set above.
325+
func setRollingUpdateParameters(controlPlaneCount int32, deployment *appsv1.Deployment) {
326+
maxUnavailable := intstr.FromInt32(max(controlPlaneCount-1, 1))
327+
maxSurge := intstr.FromInt32(controlPlaneCount)
328+
deployment.Spec.Strategy.RollingUpdate.MaxUnavailable = &maxUnavailable
329+
deployment.Spec.Strategy.RollingUpdate.MaxSurge = &maxSurge
330+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
package deployment
2+
3+
import (
4+
"testing"
5+
6+
appsv1 "k8s.io/api/apps/v1"
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/util/intstr"
9+
)
10+
11+
func TestSetRollingUpdateParameters(t *testing.T) {
12+
testCases := []struct {
13+
name string
14+
controlPlaneCount int32
15+
expectedMaxUnavailable int32
16+
expectedMaxSurge int32
17+
}{
18+
{
19+
name: "single control plane node",
20+
controlPlaneCount: 1,
21+
expectedMaxUnavailable: 1, // max(1-1, 1) = max(0, 1) = 1
22+
expectedMaxSurge: 1,
23+
},
24+
{
25+
name: "two control plane nodes",
26+
controlPlaneCount: 2,
27+
expectedMaxUnavailable: 1, // max(2-1, 1) = max(1, 1) = 1
28+
expectedMaxSurge: 2,
29+
},
30+
{
31+
name: "three control plane nodes",
32+
controlPlaneCount: 3,
33+
expectedMaxUnavailable: 2, // max(3-1, 1) = max(2, 1) = 2
34+
expectedMaxSurge: 3,
35+
},
36+
{
37+
name: "four control plane nodes",
38+
controlPlaneCount: 4,
39+
expectedMaxUnavailable: 3, // max(4-1, 1) = max(3, 1) = 3
40+
expectedMaxSurge: 4,
41+
},
42+
{
43+
name: "five control plane nodes",
44+
controlPlaneCount: 5,
45+
expectedMaxUnavailable: 4, // max(5-1, 1) = max(4, 1) = 4
46+
expectedMaxSurge: 5,
47+
},
48+
}
49+
50+
for _, tc := range testCases {
51+
t.Run(tc.name, func(t *testing.T) {
52+
// Create a test deployment with rolling update strategy
53+
deployment := &appsv1.Deployment{
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: "test-deployment",
56+
Namespace: "test-namespace",
57+
},
58+
Spec: appsv1.DeploymentSpec{
59+
Strategy: appsv1.DeploymentStrategy{
60+
Type: appsv1.RollingUpdateDeploymentStrategyType,
61+
RollingUpdate: &appsv1.RollingUpdateDeployment{
62+
MaxUnavailable: &intstr.IntOrString{Type: intstr.Int, IntVal: 0},
63+
MaxSurge: &intstr.IntOrString{Type: intstr.Int, IntVal: 0},
64+
},
65+
},
66+
},
67+
}
68+
69+
// Call the function under test
70+
setRollingUpdateParameters(tc.controlPlaneCount, deployment)
71+
72+
// Verify MaxUnavailable is set correctly
73+
if deployment.Spec.Strategy.RollingUpdate.MaxUnavailable == nil {
74+
t.Errorf("MaxUnavailable should not be nil")
75+
} else {
76+
actualMaxUnavailable := deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.IntVal
77+
if actualMaxUnavailable != tc.expectedMaxUnavailable {
78+
t.Errorf("Expected MaxUnavailable to be %d, got %d", tc.expectedMaxUnavailable, actualMaxUnavailable)
79+
}
80+
}
81+
82+
// Verify MaxSurge is set correctly
83+
if deployment.Spec.Strategy.RollingUpdate.MaxSurge == nil {
84+
t.Errorf("MaxSurge should not be nil")
85+
} else {
86+
actualMaxSurge := deployment.Spec.Strategy.RollingUpdate.MaxSurge.IntVal
87+
if actualMaxSurge != tc.expectedMaxSurge {
88+
t.Errorf("Expected MaxSurge to be %d, got %d", tc.expectedMaxSurge, actualMaxSurge)
89+
}
90+
}
91+
92+
// Verify the values are of type Int (not String)
93+
if deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.Type != intstr.Int {
94+
t.Errorf("Expected MaxUnavailable to be of type Int, got %v", deployment.Spec.Strategy.RollingUpdate.MaxUnavailable.Type)
95+
}
96+
if deployment.Spec.Strategy.RollingUpdate.MaxSurge.Type != intstr.Int {
97+
t.Errorf("Expected MaxSurge to be of type Int, got %v", deployment.Spec.Strategy.RollingUpdate.MaxSurge.Type)
98+
}
99+
})
100+
}
101+
}

0 commit comments

Comments
 (0)