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:

committed by
vinay kulkarni

parent
231849a908
commit
f2bd94a0de
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
|
Reference in New Issue
Block a user