Skip to content

Commit 444ba7b

Browse files
committed
Deployment controller should count terminating pods in the status
1 parent debec94 commit 444ba7b

File tree

5 files changed

+146
-13
lines changed

5 files changed

+146
-13
lines changed

pkg/controller/deployment/sync.go

+6
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ import (
2727
v1 "k8s.io/api/core/v1"
2828
"k8s.io/apimachinery/pkg/api/errors"
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3031
"k8s.io/klog/v2"
3132
"k8s.io/kubernetes/pkg/controller"
3233
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
34+
"k8s.io/kubernetes/pkg/features"
3335
labelsutil "k8s.io/kubernetes/pkg/util/labels"
36+
"k8s.io/utils/ptr"
3437
)
3538

3639
// syncStatusOnly only updates Deployments Status and doesn't take any mutating actions.
@@ -504,6 +507,9 @@ func calculateStatus(allRSs []*apps.ReplicaSet, newRS *apps.ReplicaSet, deployme
504507
UnavailableReplicas: unavailableReplicas,
505508
CollisionCount: deployment.Status.CollisionCount,
506509
}
510+
if utilfeature.DefaultFeatureGate.Enabled(features.DeploymentPodReplacementPolicy) {
511+
status.TerminatingReplicas = ptr.To(deploymentutil.GetTerminatingReplicaCountForReplicaSets(allRSs))
512+
}
507513

508514
// Copy conditions one by one so we won't mutate the original object.
509515
conditions := deployment.Status.Conditions

pkg/controller/deployment/util/deployment_util.go

+12
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"k8s.io/kubernetes/pkg/controller"
4242
labelsutil "k8s.io/kubernetes/pkg/util/labels"
4343
"k8s.io/utils/integer"
44+
"k8s.io/utils/ptr"
4445
)
4546

4647
const (
@@ -714,6 +715,17 @@ func GetAvailableReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int3
714715
return totalAvailableReplicas
715716
}
716717

718+
// GetTerminatingReplicaCountForReplicaSets returns the number of terminating pods for all replica sets.
719+
func GetTerminatingReplicaCountForReplicaSets(replicaSets []*apps.ReplicaSet) int32 {
720+
terminatingReplicas := int32(0)
721+
for _, rs := range replicaSets {
722+
if rs != nil {
723+
terminatingReplicas += ptr.Deref(rs.Status.TerminatingReplicas, 0)
724+
}
725+
}
726+
return terminatingReplicas
727+
}
728+
717729
// IsRollingUpdate returns true if the strategy type is a rolling update.
718730
func IsRollingUpdate(deployment *apps.Deployment) bool {
719731
return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType

pkg/controller/deployment/util/deployment_util_test.go

+20-7
Original file line numberDiff line numberDiff line change
@@ -347,27 +347,36 @@ func TestGetReplicaCountForReplicaSets(t *testing.T) {
347347
rs1 := generateRS(generateDeployment("foo"))
348348
*(rs1.Spec.Replicas) = 1
349349
rs1.Status.Replicas = 2
350+
rs1.Status.TerminatingReplicas = ptr.To[int32](3)
350351
rs2 := generateRS(generateDeployment("bar"))
351352
*(rs2.Spec.Replicas) = 2
352353
rs2.Status.Replicas = 3
354+
rs2.Status.TerminatingReplicas = ptr.To[int32](1)
355+
rs3 := generateRS(generateDeployment("baz"))
356+
*(rs3.Spec.Replicas) = 0
357+
rs3.Status.Replicas = 0
358+
rs3.Status.TerminatingReplicas = nil
353359

354360
tests := []struct {
355-
Name string
356-
sets []*apps.ReplicaSet
357-
expectedCount int32
358-
expectedActual int32
361+
Name string
362+
sets []*apps.ReplicaSet
363+
expectedCount int32
364+
expectedActual int32
365+
expectedTerminating int32
359366
}{
360367
{
361-
"1:2 Replicas",
368+
"1:2:3 Replicas",
362369
[]*apps.ReplicaSet{&rs1},
363370
1,
364371
2,
372+
3,
365373
},
366374
{
367-
"3:5 Replicas",
368-
[]*apps.ReplicaSet{&rs1, &rs2},
375+
"3:5:4 Replicas",
376+
[]*apps.ReplicaSet{&rs1, &rs2, &rs3},
369377
3,
370378
5,
379+
4,
371380
},
372381
}
373382

@@ -381,6 +390,10 @@ func TestGetReplicaCountForReplicaSets(t *testing.T) {
381390
if rs != test.expectedActual {
382391
t.Errorf("In test case %s, expectedActual %+v, got %+v", test.Name, test.expectedActual, rs)
383392
}
393+
rs = GetTerminatingReplicaCountForReplicaSets(test.sets)
394+
if rs != test.expectedTerminating {
395+
t.Errorf("In test case %s, expectedTerminating %+v, got %+v", test.Name, test.expectedTerminating, rs)
396+
}
384397
})
385398
}
386399
}

test/integration/deployment/deployment_test.go

+101-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,12 @@ import (
2828
"k8s.io/apimachinery/pkg/util/intstr"
2929
"k8s.io/apimachinery/pkg/util/uuid"
3030
"k8s.io/apimachinery/pkg/util/wait"
31+
utilfeature "k8s.io/apiserver/pkg/util/feature"
3132
"k8s.io/client-go/util/retry"
33+
featuregatetesting "k8s.io/component-base/featuregate/testing"
3234
"k8s.io/klog/v2/ktesting"
3335
deploymentutil "k8s.io/kubernetes/pkg/controller/deployment/util"
36+
"k8s.io/kubernetes/pkg/features"
3437
"k8s.io/kubernetes/test/integration/framework"
3538
testutil "k8s.io/kubernetes/test/utils"
3639
"k8s.io/utils/ptr"
@@ -965,7 +968,7 @@ func TestDeploymentAvailableCondition(t *testing.T) {
965968
}
966969

967970
// Verify all replicas fields of DeploymentStatus have desired counts
968-
if err = tester.checkDeploymentStatusReplicasFields(10, 10, 0, 0, 10); err != nil {
971+
if err = tester.checkDeploymentStatusReplicasFields(10, 10, 0, 0, 10, nil); err != nil {
969972
t.Fatal(err)
970973
}
971974

@@ -985,7 +988,7 @@ func TestDeploymentAvailableCondition(t *testing.T) {
985988
}
986989

987990
// Verify all replicas fields of DeploymentStatus have desired counts
988-
if err = tester.checkDeploymentStatusReplicasFields(10, 10, 10, 0, 10); err != nil {
991+
if err = tester.checkDeploymentStatusReplicasFields(10, 10, 10, 0, 10, nil); err != nil {
989992
t.Fatal(err)
990993
}
991994

@@ -1008,7 +1011,7 @@ func TestDeploymentAvailableCondition(t *testing.T) {
10081011
}
10091012

10101013
// Verify all replicas fields of DeploymentStatus have desired counts
1011-
if err = tester.checkDeploymentStatusReplicasFields(10, 10, 10, 10, 0); err != nil {
1014+
if err = tester.checkDeploymentStatusReplicasFields(10, 10, 10, 10, 0, nil); err != nil {
10121015
t.Fatal(err)
10131016
}
10141017
}
@@ -1303,3 +1306,98 @@ func TestReplicaSetOrphaningAndAdoptionWhenLabelsChange(t *testing.T) {
13031306
t.Fatalf("failed waiting for replicaset adoption by deployment %q to complete: %v", deploymentName, err)
13041307
}
13051308
}
1309+
1310+
func TestTerminatingReplicasDeploymentStatus(t *testing.T) {
1311+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeploymentPodReplacementPolicy, false)
1312+
1313+
_, ctx := ktesting.NewTestContext(t)
1314+
ctx, cancel := context.WithCancel(ctx)
1315+
defer cancel()
1316+
1317+
closeFn, rm, dc, informers, c := dcSetup(ctx, t)
1318+
defer closeFn()
1319+
1320+
name := "test-terminating-replica-status"
1321+
ns := framework.CreateNamespaceOrDie(c, name, t)
1322+
defer framework.DeleteNamespaceOrDie(c, ns, t)
1323+
1324+
deploymentName := "deployment"
1325+
replicas := int32(6)
1326+
tester := &deploymentTester{t: t, c: c, deployment: newDeployment(deploymentName, ns.Name, replicas)}
1327+
tester.deployment.Spec.Strategy.Type = apps.RecreateDeploymentStrategyType
1328+
tester.deployment.Spec.Strategy.RollingUpdate = nil
1329+
tester.deployment.Spec.Template.Spec.NodeName = "fake-node"
1330+
tester.deployment.Spec.Template.Spec.TerminationGracePeriodSeconds = ptr.To(int64(300))
1331+
1332+
var err error
1333+
tester.deployment, err = c.AppsV1().Deployments(ns.Name).Create(context.TODO(), tester.deployment, metav1.CreateOptions{})
1334+
if err != nil {
1335+
t.Fatalf("failed to create deployment %q: %v", deploymentName, err)
1336+
}
1337+
1338+
// Start informer and controllers
1339+
stopControllers := runControllersAndInformers(t, rm, dc, informers)
1340+
defer stopControllers()
1341+
1342+
// Ensure the deployment completes while marking its pods as ready simultaneously
1343+
if err := tester.waitForDeploymentCompleteAndMarkPodsReady(); err != nil {
1344+
t.Fatal(err)
1345+
}
1346+
// Should not update terminating replicas when feature gate is disabled
1347+
// Verify all replicas fields of DeploymentStatus have desired counts
1348+
if err = tester.checkDeploymentStatusReplicasFields(6, 6, 6, 6, 0, nil); err != nil {
1349+
t.Fatal(err)
1350+
}
1351+
1352+
// Scale down the deployment
1353+
tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
1354+
update.Spec.Replicas = ptr.To(int32(4))
1355+
})
1356+
if err != nil {
1357+
t.Fatalf("failed updating deployment %q: %v", deploymentName, err)
1358+
}
1359+
// Wait for number of ready replicas to equal number of replicas.
1360+
if err = tester.waitForReadyReplicas(); err != nil {
1361+
t.Fatal(err)
1362+
}
1363+
// Verify all replicas fields of DeploymentStatus have desired counts
1364+
if err = tester.checkDeploymentStatusReplicasFields(4, 4, 4, 4, 0, nil); err != nil {
1365+
t.Fatal(err)
1366+
}
1367+
1368+
// should update terminating replicas when feature gate is enabled
1369+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeploymentPodReplacementPolicy, true)
1370+
// Scale down the deployment
1371+
tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
1372+
update.Spec.Replicas = ptr.To(int32(3))
1373+
})
1374+
if err != nil {
1375+
t.Fatalf("failed updating deployment %q: %v", deploymentName, err)
1376+
}
1377+
// Wait for number of ready replicas to equal number of replicas.
1378+
if err = tester.waitForReadyReplicas(); err != nil {
1379+
t.Fatal(err)
1380+
}
1381+
// Verify all replicas fields of DeploymentStatus have desired counts
1382+
if err = tester.checkDeploymentStatusReplicasFields(3, 3, 3, 3, 0, ptr.To[int32](3)); err != nil {
1383+
t.Fatal(err)
1384+
}
1385+
1386+
// should not update terminating replicas when feature gate is disabled
1387+
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DeploymentPodReplacementPolicy, false)
1388+
// Scale down the deployment
1389+
tester.deployment, err = tester.updateDeployment(func(update *apps.Deployment) {
1390+
update.Spec.Replicas = ptr.To(int32(2))
1391+
})
1392+
if err != nil {
1393+
t.Fatalf("failed updating deployment %q: %v", deploymentName, err)
1394+
}
1395+
// Wait for number of ready replicas to equal number of replicas.
1396+
if err = tester.waitForReadyReplicas(); err != nil {
1397+
t.Fatal(err)
1398+
}
1399+
// Verify all replicas fields of DeploymentStatus have desired counts
1400+
if err = tester.checkDeploymentStatusReplicasFields(2, 2, 2, 2, 0, nil); err != nil {
1401+
t.Fatal(err)
1402+
}
1403+
}

test/integration/deployment/util.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"k8s.io/kubernetes/pkg/controller/replicaset"
3838
"k8s.io/kubernetes/test/integration/framework"
3939
testutil "k8s.io/kubernetes/test/utils"
40+
"k8s.io/utils/ptr"
4041
)
4142

4243
const (
@@ -433,7 +434,7 @@ func (d *deploymentTester) markUpdatedPodsReadyWithoutComplete() error {
433434

434435
// Verify all replicas fields of DeploymentStatus have desired count.
435436
// Immediately return an error when found a non-matching replicas field.
436-
func (d *deploymentTester) checkDeploymentStatusReplicasFields(replicas, updatedReplicas, readyReplicas, availableReplicas, unavailableReplicas int32) error {
437+
func (d *deploymentTester) checkDeploymentStatusReplicasFields(replicas, updatedReplicas, readyReplicas, availableReplicas, unavailableReplicas int32, terminatingReplicas *int32) error {
437438
deployment, err := d.c.AppsV1().Deployments(d.deployment.Namespace).Get(context.TODO(), d.deployment.Name, metav1.GetOptions{})
438439
if err != nil {
439440
return fmt.Errorf("failed to get deployment %q: %v", d.deployment.Name, err)
@@ -448,10 +449,13 @@ func (d *deploymentTester) checkDeploymentStatusReplicasFields(replicas, updated
448449
return fmt.Errorf("unexpected .readyReplicas: expect %d, got %d", readyReplicas, deployment.Status.ReadyReplicas)
449450
}
450451
if deployment.Status.AvailableReplicas != availableReplicas {
451-
return fmt.Errorf("unexpected .replicas: expect %d, got %d", availableReplicas, deployment.Status.AvailableReplicas)
452+
return fmt.Errorf("unexpected .availableReplicas: expect %d, got %d", availableReplicas, deployment.Status.AvailableReplicas)
452453
}
453454
if deployment.Status.UnavailableReplicas != unavailableReplicas {
454-
return fmt.Errorf("unexpected .replicas: expect %d, got %d", unavailableReplicas, deployment.Status.UnavailableReplicas)
455+
return fmt.Errorf("unexpected .unavailableReplicas: expect %d, got %d", unavailableReplicas, deployment.Status.UnavailableReplicas)
456+
}
457+
if !ptr.Equal(deployment.Status.TerminatingReplicas, terminatingReplicas) {
458+
return fmt.Errorf("unexpected .terminatingReplicas: expect %v, got %v", ptr.Deref(terminatingReplicas, -1), ptr.Deref(deployment.Status.TerminatingReplicas, -1))
455459
}
456460
return nil
457461
}

0 commit comments

Comments
 (0)