In-place Pod Vertical Scaling - core implementation

1. Core Kubelet changes to implement In-place Pod Vertical Scaling.
2. E2E tests for In-place Pod Vertical Scaling.
3. Refactor kubelet code and add missing tests (Derek's kubelet review)
4. Add a new hash over container fields without Resources field to allow feature gate toggling without restarting containers not using the feature.
5. Fix corner-case where resize A->B->A gets ignored
6. Add cgroup v2 support to pod resize E2E test.
KEP: /enhancements/keps/sig-node/1287-in-place-update-pod-resources

Co-authored-by: Chen Wang <Chen.Wang1@ibm.com>
This commit is contained in:
Vinay Kulkarni
2022-11-04 13:47:33 -07:00
committed by vinay kulkarni
parent 231849a908
commit f2bd94a0de
48 changed files with 4639 additions and 56 deletions

View File

@@ -117,6 +117,23 @@ func HashContainer(container *v1.Container) uint64 {
return uint64(hash.Sum32())
}
// HashContainerWithoutResources returns the hash of the container with Resources field zero'd out.
func HashContainerWithoutResources(container *v1.Container) uint64 {
// InPlacePodVerticalScaling enables mutable Resources field.
// Changes to this field may not require container restart depending on policy.
// Compute hash over fields besides the Resources field
// NOTE: This is needed during alpha and beta so that containers using Resources but
// not subject to In-place resize are not unexpectedly restarted when
// InPlacePodVerticalScaling feature-gate is toggled.
//TODO(vinaykul,InPlacePodVerticalScaling): Remove this in GA+1 and make HashContainerWithoutResources to become Hash.
hashWithoutResources := fnv.New32a()
containerCopy := container.DeepCopy()
containerCopy.Resources = v1.ResourceRequirements{}
containerJSON, _ := json.Marshal(containerCopy)
hashutil.DeepHashObject(hashWithoutResources, containerJSON)
return uint64(hashWithoutResources.Sum32())
}
// envVarsToMap constructs a map of environment name to value from a slice
// of env vars.
func envVarsToMap(envs []EnvVar) map[string]string {
@@ -252,12 +269,13 @@ func ConvertPodStatusToRunningPod(runtimeName string, podStatus *PodStatus) Pod
continue
}
container := &Container{
ID: containerStatus.ID,
Name: containerStatus.Name,
Image: containerStatus.Image,
ImageID: containerStatus.ImageID,
Hash: containerStatus.Hash,
State: containerStatus.State,
ID: containerStatus.ID,
Name: containerStatus.Name,
Image: containerStatus.Image,
ImageID: containerStatus.ImageID,
Hash: containerStatus.Hash,
HashWithoutResources: containerStatus.HashWithoutResources,
State: containerStatus.State,
}
runningPod.Containers = append(runningPod.Containers, container)
}

View File

@@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -908,3 +909,83 @@ func TestHasWindowsHostProcessContainer(t *testing.T) {
})
}
}
func TestHashContainerWithoutResources(t *testing.T) {
cpu100m := resource.MustParse("100m")
cpu200m := resource.MustParse("200m")
mem100M := resource.MustParse("100Mi")
mem200M := resource.MustParse("200Mi")
cpuPolicyRestartNotRequired := v1.ContainerResizePolicy{ResourceName: v1.ResourceCPU, Policy: v1.RestartNotRequired}
memPolicyRestartNotRequired := v1.ContainerResizePolicy{ResourceName: v1.ResourceMemory, Policy: v1.RestartNotRequired}
cpuPolicyRestartRequired := v1.ContainerResizePolicy{ResourceName: v1.ResourceCPU, Policy: v1.RestartRequired}
memPolicyRestartRequired := v1.ContainerResizePolicy{ResourceName: v1.ResourceMemory, Policy: v1.RestartRequired}
type testCase struct {
name string
container *v1.Container
expectedHash uint64
}
tests := []testCase{
{
"Burstable pod with CPU policy restart required",
&v1.Container{
Name: "foo",
Image: "bar",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceCPU: cpu200m, v1.ResourceMemory: mem200M},
Requests: v1.ResourceList{v1.ResourceCPU: cpu100m, v1.ResourceMemory: mem100M},
},
ResizePolicy: []v1.ContainerResizePolicy{cpuPolicyRestartRequired, memPolicyRestartNotRequired},
},
0x86a4393c,
},
{
"Burstable pod with memory policy restart required",
&v1.Container{
Name: "foo",
Image: "bar",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceCPU: cpu200m, v1.ResourceMemory: mem200M},
Requests: v1.ResourceList{v1.ResourceCPU: cpu100m, v1.ResourceMemory: mem100M},
},
ResizePolicy: []v1.ContainerResizePolicy{cpuPolicyRestartNotRequired, memPolicyRestartRequired},
},
0x73a18cce,
},
{
"Guaranteed pod with CPU policy restart required",
&v1.Container{
Name: "foo",
Image: "bar",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceCPU: cpu100m, v1.ResourceMemory: mem100M},
Requests: v1.ResourceList{v1.ResourceCPU: cpu100m, v1.ResourceMemory: mem100M},
},
ResizePolicy: []v1.ContainerResizePolicy{cpuPolicyRestartRequired, memPolicyRestartNotRequired},
},
0x86a4393c,
},
{
"Guaranteed pod with memory policy restart required",
&v1.Container{
Name: "foo",
Image: "bar",
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{v1.ResourceCPU: cpu100m, v1.ResourceMemory: mem100M},
Requests: v1.ResourceList{v1.ResourceCPU: cpu100m, v1.ResourceMemory: mem100M},
},
ResizePolicy: []v1.ContainerResizePolicy{cpuPolicyRestartNotRequired, memPolicyRestartRequired},
},
0x73a18cce,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
containerCopy := tc.container.DeepCopy()
hash := HashContainerWithoutResources(tc.container)
assert.Equal(t, tc.expectedHash, hash, "[%s]", tc.name)
assert.Equal(t, containerCopy, tc.container, "[%s]", tc.name)
})
}
}

View File

@@ -27,6 +27,7 @@ import (
"time"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/client-go/util/flowcontrol"
@@ -295,6 +296,11 @@ type Container struct {
// Hash of the container, used for comparison. Optional for containers
// not managed by kubelet.
Hash uint64
// Hash of the container over fields with Resources field zero'd out.
// NOTE: This is needed during alpha and beta so that containers using Resources are
// not unexpectedly restarted when InPlacePodVerticalScaling feature-gate is toggled.
//TODO(vinaykul,InPlacePodVerticalScaling): Remove this in GA+1 and make HashWithoutResources to become Hash.
HashWithoutResources uint64
// State is the state of the container.
State State
}
@@ -319,6 +325,18 @@ type PodStatus struct {
TimeStamp time.Time
}
// ContainerResources represents the Resources allocated to the running container.
type ContainerResources struct {
// CPU capacity reserved for the container (cpu.shares)
CPURequest *resource.Quantity
// CPU limit enforced on the container (cpu.cfs_quota_us)
CPULimit *resource.Quantity
// Memory capaacity reserved for the container
MemoryRequest *resource.Quantity
// Memory limit enforced on the container (memory.limit_in_bytes)
MemoryLimit *resource.Quantity
}
// Status represents the status of a container.
type Status struct {
// ID of the container.
@@ -342,6 +360,8 @@ type Status struct {
ImageID string
// Hash of the container, used for comparison.
Hash uint64
// Hash of the container over fields with Resources field zero'd out.
HashWithoutResources uint64
// Number of times that the container has been restarted.
RestartCount int
// A string explains why container is in such a status.
@@ -349,6 +369,8 @@ type Status struct {
// Message written by the container before exiting (stored in
// TerminationMessagePath).
Message string
// CPU and memory resources for this container
Resources *ContainerResources
}
// FindContainerStatusByName returns container status in the pod status with the given name.