Skip to content

Commit b252865

Browse files
kubelet: Avoid sending no-op patches
In an e2e run, out of 1857 pod status updates executed by the Kubelet 453 (25%) were no-ops - they only contained the UID of the pod and no status changes. If the patch is a no-op we can avoid invoking the server and continue.
1 parent 5ceddce commit b252865

File tree

4 files changed

+51
-29
lines changed

4 files changed

+51
-29
lines changed

pkg/kubelet/status/status_manager.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,15 +541,19 @@ func (m *manager) syncPod(uid types.UID, status versionedPodStatus) {
541541
}
542542

543543
oldStatus := pod.Status.DeepCopy()
544-
newPod, patchBytes, err := statusutil.PatchPodStatus(m.kubeClient, pod.Namespace, pod.Name, pod.UID, *oldStatus, mergePodStatus(*oldStatus, status.status))
544+
newPod, patchBytes, unchanged, err := statusutil.PatchPodStatus(m.kubeClient, pod.Namespace, pod.Name, pod.UID, *oldStatus, mergePodStatus(*oldStatus, status.status))
545545
klog.V(3).Infof("Patch status for pod %q with %q", format.Pod(pod), patchBytes)
546546
if err != nil {
547547
klog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err)
548548
return
549549
}
550-
pod = newPod
550+
if unchanged {
551+
klog.V(3).Infof("Status for pod %q is up-to-date: (%d)", format.Pod(pod), status.version)
552+
} else {
553+
klog.V(3).Infof("Status for pod %q updated successfully: (%d, %+v)", format.Pod(pod), status.version, status.status)
554+
pod = newPod
555+
}
551556

552-
klog.V(3).Infof("Status for pod %q updated successfully: (%d, %+v)", format.Pod(pod), status.version, status.status)
553557
m.apiStatusVersions[kubetypes.MirrorPodUID(pod.UID)] = status.version
554558

555559
// We don't handle graceful deletion of mirror pods.

pkg/kubelet/status/status_manager_test.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func getRandomPodStatus() v1.PodStatus {
9696
}
9797

9898
func verifyActions(t *testing.T, manager *manager, expectedActions []core.Action) {
99+
t.Helper()
99100
manager.consumeUpdates()
100101
actions := manager.kubeClient.(*fake.Clientset).Actions()
101102
defer manager.kubeClient.(*fake.Clientset).ClearActions()
@@ -401,17 +402,17 @@ func TestStaleUpdates(t *testing.T) {
401402
t.Logf("Nothing left in the channel to sync")
402403
verifyActions(t, m, []core.Action{})
403404

404-
t.Log("Unchanged status should not send an update.")
405+
t.Log("Unchanged status should not send an update")
405406
m.SetPodStatus(pod, status)
406407
verifyUpdates(t, m, 0)
407408

408-
t.Log("... unless it's stale.")
409+
t.Log("... even if it's stale as long as nothing changes")
409410
mirrorPodUID := kubetypes.MirrorPodUID(pod.UID)
410411
m.apiStatusVersions[mirrorPodUID] = m.apiStatusVersions[mirrorPodUID] - 1
411412

412413
m.SetPodStatus(pod, status)
413414
m.syncBatch()
414-
verifyActions(t, m, []core.Action{getAction(), patchAction()})
415+
verifyActions(t, m, []core.Action{getAction()})
415416

416417
t.Logf("Nothing stuck in the pipe.")
417418
verifyUpdates(t, m, 0)
@@ -821,8 +822,9 @@ func TestReconcilePodStatus(t *testing.T) {
821822
t.Logf("If the pod status is the same, a reconciliation is not needed and syncBatch should do nothing")
822823
syncer.podManager.UpdatePod(testPod)
823824
if syncer.needsReconcile(testPod.UID, podStatus) {
824-
t.Errorf("Pod status is the same, a reconciliation is not needed")
825+
t.Fatalf("Pod status is the same, a reconciliation is not needed")
825826
}
827+
syncer.SetPodStatus(testPod, podStatus)
826828
syncer.syncBatch()
827829
verifyActions(t, syncer, []core.Action{})
828830

@@ -835,17 +837,19 @@ func TestReconcilePodStatus(t *testing.T) {
835837
testPod.Status.StartTime = &normalizedStartTime
836838
syncer.podManager.UpdatePod(testPod)
837839
if syncer.needsReconcile(testPod.UID, podStatus) {
838-
t.Errorf("Pod status only differs for timestamp format, a reconciliation is not needed")
840+
t.Fatalf("Pod status only differs for timestamp format, a reconciliation is not needed")
839841
}
842+
syncer.SetPodStatus(testPod, podStatus)
840843
syncer.syncBatch()
841844
verifyActions(t, syncer, []core.Action{})
842845

843846
t.Logf("If the pod status is different, a reconciliation is needed, syncBatch should trigger an update")
844-
testPod.Status = getRandomPodStatus()
847+
changedPodStatus := getRandomPodStatus()
845848
syncer.podManager.UpdatePod(testPod)
846-
if !syncer.needsReconcile(testPod.UID, podStatus) {
847-
t.Errorf("Pod status is different, a reconciliation is needed")
849+
if !syncer.needsReconcile(testPod.UID, changedPodStatus) {
850+
t.Fatalf("Pod status is different, a reconciliation is needed")
848851
}
852+
syncer.SetPodStatus(testPod, changedPodStatus)
849853
syncer.syncBatch()
850854
verifyActions(t, syncer, []core.Action{getAction(), patchAction()})
851855
}

pkg/util/pod/pod.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package pod
1818

1919
import (
20+
"bytes"
2021
"context"
2122
"encoding/json"
2223
"fmt"
@@ -28,39 +29,42 @@ import (
2829
clientset "k8s.io/client-go/kubernetes"
2930
)
3031

31-
// PatchPodStatus patches pod status.
32-
func PatchPodStatus(c clientset.Interface, namespace, name string, uid types.UID, oldPodStatus, newPodStatus v1.PodStatus) (*v1.Pod, []byte, error) {
33-
patchBytes, err := preparePatchBytesForPodStatus(namespace, name, uid, oldPodStatus, newPodStatus)
32+
// PatchPodStatus patches pod status. It returns true and avoids an update if the patch contains no changes.
33+
func PatchPodStatus(c clientset.Interface, namespace, name string, uid types.UID, oldPodStatus, newPodStatus v1.PodStatus) (*v1.Pod, []byte, bool, error) {
34+
patchBytes, unchanged, err := preparePatchBytesForPodStatus(namespace, name, uid, oldPodStatus, newPodStatus)
3435
if err != nil {
35-
return nil, nil, err
36+
return nil, nil, false, err
37+
}
38+
if unchanged {
39+
return nil, patchBytes, true, nil
3640
}
3741

3842
updatedPod, err := c.CoreV1().Pods(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}, "status")
3943
if err != nil {
40-
return nil, nil, fmt.Errorf("failed to patch status %q for pod %q/%q: %v", patchBytes, namespace, name, err)
44+
return nil, nil, false, fmt.Errorf("failed to patch status %q for pod %q/%q: %v", patchBytes, namespace, name, err)
4145
}
42-
return updatedPod, patchBytes, nil
46+
return updatedPod, patchBytes, false, nil
4347
}
4448

45-
func preparePatchBytesForPodStatus(namespace, name string, uid types.UID, oldPodStatus, newPodStatus v1.PodStatus) ([]byte, error) {
49+
func preparePatchBytesForPodStatus(namespace, name string, uid types.UID, oldPodStatus, newPodStatus v1.PodStatus) ([]byte, bool, error) {
4650
oldData, err := json.Marshal(v1.Pod{
4751
Status: oldPodStatus,
4852
})
4953
if err != nil {
50-
return nil, fmt.Errorf("failed to Marshal oldData for pod %q/%q: %v", namespace, name, err)
54+
return nil, false, fmt.Errorf("failed to Marshal oldData for pod %q/%q: %v", namespace, name, err)
5155
}
5256

5357
newData, err := json.Marshal(v1.Pod{
5458
ObjectMeta: metav1.ObjectMeta{UID: uid}, // only put the uid in the new object to ensure it appears in the patch as a precondition
5559
Status: newPodStatus,
5660
})
5761
if err != nil {
58-
return nil, fmt.Errorf("failed to Marshal newData for pod %q/%q: %v", namespace, name, err)
62+
return nil, false, fmt.Errorf("failed to Marshal newData for pod %q/%q: %v", namespace, name, err)
5963
}
6064

6165
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Pod{})
6266
if err != nil {
63-
return nil, fmt.Errorf("failed to CreateTwoWayMergePatch for pod %q/%q: %v", namespace, name, err)
67+
return nil, false, fmt.Errorf("failed to CreateTwoWayMergePatch for pod %q/%q: %v", namespace, name, err)
6468
}
65-
return patchBytes, nil
69+
return patchBytes, bytes.Equal(patchBytes, []byte(fmt.Sprintf(`{"metadata":{"uid":%q}}`, uid))), nil
6670
}

pkg/util/pod/pod_test.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,13 @@ func TestPatchPodStatus(t *testing.T) {
4444
testCases := []struct {
4545
description string
4646
mutate func(input v1.PodStatus) v1.PodStatus
47+
expectUnchanged bool
4748
expectedPatchBytes []byte
4849
}{
4950
{
5051
"no change",
5152
func(input v1.PodStatus) v1.PodStatus { return input },
53+
true,
5254
[]byte(fmt.Sprintf(`{"metadata":{"uid":"myuid"}}`)),
5355
},
5456
{
@@ -57,6 +59,7 @@ func TestPatchPodStatus(t *testing.T) {
5759
input.Message = "random message"
5860
return input
5961
},
62+
false,
6063
[]byte(fmt.Sprintf(`{"metadata":{"uid":"myuid"},"status":{"message":"random message"}}`)),
6164
},
6265
{
@@ -65,6 +68,7 @@ func TestPatchPodStatus(t *testing.T) {
6568
input.Conditions[0].Status = v1.ConditionFalse
6669
return input
6770
},
71+
false,
6872
[]byte(fmt.Sprintf(`{"metadata":{"uid":"myuid"},"status":{"$setElementOrder/conditions":[{"type":"Ready"},{"type":"PodScheduled"}],"conditions":[{"status":"False","type":"Ready"}]}}`)),
6973
},
7074
{
@@ -78,17 +82,23 @@ func TestPatchPodStatus(t *testing.T) {
7882
}
7983
return input
8084
},
85+
false,
8186
[]byte(fmt.Sprintf(`{"metadata":{"uid":"myuid"},"status":{"initContainerStatuses":[{"image":"","imageID":"","lastState":{},"name":"init-container","ready":true,"restartCount":0,"state":{}}]}}`)),
8287
},
8388
}
8489
for _, tc := range testCases {
85-
_, patchBytes, err := PatchPodStatus(client, ns, name, uid, getPodStatus(), tc.mutate(getPodStatus()))
86-
if err != nil {
87-
t.Errorf("unexpected error: %v", err)
88-
}
89-
if !reflect.DeepEqual(patchBytes, tc.expectedPatchBytes) {
90-
t.Errorf("for test case %q, expect patchBytes: %q, got: %q\n", tc.description, tc.expectedPatchBytes, patchBytes)
91-
}
90+
t.Run(tc.description, func(t *testing.T) {
91+
_, patchBytes, unchanged, err := PatchPodStatus(client, ns, name, uid, getPodStatus(), tc.mutate(getPodStatus()))
92+
if err != nil {
93+
t.Errorf("unexpected error: %v", err)
94+
}
95+
if unchanged != tc.expectUnchanged {
96+
t.Errorf("unexpected change: %t", unchanged)
97+
}
98+
if !reflect.DeepEqual(patchBytes, tc.expectedPatchBytes) {
99+
t.Errorf("expect patchBytes: %q, got: %q\n", tc.expectedPatchBytes, patchBytes)
100+
}
101+
})
92102
}
93103
}
94104

0 commit comments

Comments
 (0)