Skip to content

Commit 2a99bfc

Browse files
committed
node: cm: don't share containerMap instances between managers
Since the GA graduation of memory manager in kubernetes#128517 we are sharing the initial container map across managers. The intention of this sharing was not to actually share a data structure, but 1. save the relatively expensive relisting from runtime 2. have all the managers share a consistent view - even though the chance for misalignement tend to be tiny. The unwanted side effect though is now all the managers race to modify a data shared, not thread safe data structure. The fix is to clone (deepcopy) the computed map when passing it to each manager. This restores the old semantic of the code. This issue brings the topic of possibly managers go out of sync since each of them maintain a private view of the world. This risk is real, yet this is how the code worked for most of the lifetime, so the plan is to look at this and evaluate possible improvements later on. Signed-off-by: Francesco Romani <[email protected]>
1 parent 09e5e62 commit 2a99bfc

File tree

4 files changed

+100
-12
lines changed

4 files changed

+100
-12
lines changed

pkg/kubelet/cm/container_manager_linux.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -575,13 +575,13 @@ func (cm *containerManagerImpl) Start(ctx context.Context, node *v1.Node,
575575
}
576576

577577
// Initialize CPU manager
578-
err := cm.cpuManager.Start(cpumanager.ActivePodsFunc(activePods), sourcesReady, podStatusProvider, runtimeService, containerMap)
578+
err := cm.cpuManager.Start(cpumanager.ActivePodsFunc(activePods), sourcesReady, podStatusProvider, runtimeService, containerMap.Clone())
579579
if err != nil {
580580
return fmt.Errorf("start cpu manager error: %w", err)
581581
}
582582

583583
// Initialize memory manager
584-
err = cm.memoryManager.Start(memorymanager.ActivePodsFunc(activePods), sourcesReady, podStatusProvider, runtimeService, containerMap)
584+
err = cm.memoryManager.Start(memorymanager.ActivePodsFunc(activePods), sourcesReady, podStatusProvider, runtimeService, containerMap.Clone())
585585
if err != nil {
586586
return fmt.Errorf("start memory manager error: %w", err)
587587
}
@@ -643,7 +643,7 @@ func (cm *containerManagerImpl) Start(ctx context.Context, node *v1.Node,
643643
}
644644

645645
// Starts device manager.
646-
if err := cm.deviceManager.Start(devicemanager.ActivePodsFunc(activePods), sourcesReady, containerMap, containerRunningSet); err != nil {
646+
if err := cm.deviceManager.Start(devicemanager.ActivePodsFunc(activePods), sourcesReady, containerMap.Clone(), containerRunningSet); err != nil {
647647
return err
648648
}
649649

pkg/kubelet/cm/container_manager_windows.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ package cm
2525
import (
2626
"context"
2727
"fmt"
28+
"sync"
29+
2830
utilfeature "k8s.io/apiserver/pkg/util/feature"
2931
kubefeatures "k8s.io/kubernetes/pkg/features"
3032
"k8s.io/kubernetes/pkg/kubelet/cm/memorymanager"
31-
"sync"
3233

3334
"k8s.io/klog/v2"
3435
"k8s.io/mount-utils"
@@ -96,14 +97,14 @@ func (cm *containerManagerImpl) Start(ctx context.Context, node *v1.Node,
9697

9798
// Initialize CPU manager
9899
if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WindowsCPUAndMemoryAffinity) {
99-
err := cm.cpuManager.Start(cpumanager.ActivePodsFunc(activePods), sourcesReady, podStatusProvider, runtimeService, containerMap)
100+
err := cm.cpuManager.Start(cpumanager.ActivePodsFunc(activePods), sourcesReady, podStatusProvider, runtimeService, containerMap.Clone())
100101
if err != nil {
101102
return fmt.Errorf("start cpu manager error: %v", err)
102103
}
103104
}
104105

105106
// Starts device manager.
106-
if err := cm.deviceManager.Start(devicemanager.ActivePodsFunc(activePods), sourcesReady, containerMap, containerRunningSet); err != nil {
107+
if err := cm.deviceManager.Start(devicemanager.ActivePodsFunc(activePods), sourcesReady, containerMap.Clone(), containerRunningSet); err != nil {
107108
return err
108109
}
109110

pkg/kubelet/cm/containermap/container_map.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,35 @@ import (
2020
"fmt"
2121
)
2222

23-
// ContainerMap maps (containerID)->(*v1.Pod, *v1.Container)
24-
type ContainerMap map[string]struct {
23+
// cmItem (ContainerMap ITEM) is a pair podUID, containerName
24+
type cmItem struct {
2525
podUID string
2626
containerName string
2727
}
2828

29+
// ContainerMap maps (containerID)->(podUID, containerName)
30+
type ContainerMap map[string]cmItem
31+
2932
// NewContainerMap creates a new ContainerMap struct
3033
func NewContainerMap() ContainerMap {
3134
return make(ContainerMap)
3235
}
3336

37+
// Clone creates a deep copy of the ContainerMap
38+
func (cm ContainerMap) Clone() ContainerMap {
39+
ret := make(ContainerMap, len(cm))
40+
for key, val := range cm {
41+
ret[key] = val
42+
}
43+
return ret
44+
}
45+
3446
// Add adds a mapping of (containerID)->(podUID, containerName) to the ContainerMap
3547
func (cm ContainerMap) Add(podUID, containerName, containerID string) {
36-
cm[containerID] = struct {
37-
podUID string
38-
containerName string
39-
}{podUID, containerName}
48+
cm[containerID] = cmItem{
49+
podUID: podUID,
50+
containerName: containerName,
51+
}
4052
}
4153

4254
// RemoveByContainerID removes a mapping of (containerID)->(podUID, containerName) from the ContainerMap

pkg/kubelet/cm/containermap/container_map_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,65 @@ import (
2020
"testing"
2121
)
2222

23+
func TestContainerMapCloneEqual(t *testing.T) {
24+
cm := NewContainerMap()
25+
// add random fake data
26+
cm.Add("fakePodUID-1", "fakeContainerName-a1", "fakeContainerID-A")
27+
cm.Add("fakePodUID-2", "fakeContainerName-b2", "fakeContainerID-B")
28+
cm.Add("fakePodUID-2", "fakeContainerName-c2", "fakeContainerID-C")
29+
cm.Add("fakePodUID-3", "fakeContainerName-d3", "fakeContainerID-D")
30+
cm.Add("fakePodUID-3", "fakeContainerName-e3", "fakeContainerID-E")
31+
cm.Add("fakePodUID-3", "fakeContainerName-f3", "fakeContainerID-F")
32+
33+
cloned := cm.Clone()
34+
if !areEqual(cm, cloned) {
35+
t.Fatalf("clone %+#v different from original %+#v", cloned, cm)
36+
}
37+
}
38+
39+
func TestContainerMapCloneUnshared(t *testing.T) {
40+
cm := NewContainerMap()
41+
// add random fake data
42+
cm.Add("fakePodUID-1", "fakeContainerName-a1", "fakeContainerID-A")
43+
cm.Add("fakePodUID-2", "fakeContainerName-b2", "fakeContainerID-B")
44+
cm.Add("fakePodUID-2", "fakeContainerName-c2", "fakeContainerID-C")
45+
cm.Add("fakePodUID-3", "fakeContainerName-d3", "fakeContainerID-D")
46+
cm.Add("fakePodUID-3", "fakeContainerName-e3", "fakeContainerID-E")
47+
cm.Add("fakePodUID-3", "fakeContainerName-f3", "fakeContainerID-F")
48+
49+
// early sanity check, random ID, no special meaning
50+
podUID, containerName, err := cm.GetContainerRef("fakeContainerID-C")
51+
if err != nil {
52+
t.Fatalf("unexpected error: %v", err)
53+
}
54+
if podUID != "fakePodUID-2" || containerName != "fakeContainerName-c2" {
55+
t.Fatalf("unexpected data: uid=%q name=%q", podUID, containerName)
56+
}
57+
if cID, err := cm.GetContainerID(podUID, containerName); err != nil || cID != "fakeContainerID-C" {
58+
t.Fatalf("unexpected data: cid=%q err=%v", cID, err)
59+
}
60+
61+
cloned := cm.Clone()
62+
cloned.RemoveByContainerRef("fakePodUID-2", "fakeContainerName-c2")
63+
// check is actually gone
64+
if cID, err := cloned.GetContainerID("fakePodUID-2", "fakeContainerName-c2"); err == nil || cID != "" {
65+
t.Fatalf("unexpected data found: cid=%q", cID)
66+
}
67+
68+
// check the original copy didn't change
69+
// early sanity check, random ID, no special meaning
70+
podUIDRedo, containerNameRedo, err2 := cm.GetContainerRef("fakeContainerID-C")
71+
if err != nil {
72+
t.Fatalf("unexpected error: %v", err2)
73+
}
74+
if podUIDRedo != "fakePodUID-2" || containerNameRedo != "fakeContainerName-c2" {
75+
t.Fatalf("unexpected data: uid=%q name=%q", podUIDRedo, containerNameRedo)
76+
}
77+
if cID, err := cm.GetContainerID(podUIDRedo, containerNameRedo); err != nil || cID != "fakeContainerID-C" {
78+
t.Fatalf("unexpected data: cid=%q", cID)
79+
}
80+
}
81+
2382
func TestContainerMap(t *testing.T) {
2483
testCases := []struct {
2584
podUID string
@@ -84,3 +143,19 @@ func TestContainerMap(t *testing.T) {
84143
}
85144
}
86145
}
146+
147+
func areEqual(cm1, cm2 ContainerMap) bool {
148+
if len(cm1) != len(cm2) {
149+
return false
150+
}
151+
for key1, item1 := range cm1 {
152+
item2, ok := cm2[key1]
153+
if !ok {
154+
return false
155+
}
156+
if item1 != item2 {
157+
return false
158+
}
159+
}
160+
return true
161+
}

0 commit comments

Comments
 (0)