Commit 46504c20 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #14943 from vishh/qos

Automatic merge from submit-queue Updaing QoS policy to be at the pod level Quality of Service will be derived from an entire Pod Spec, instead of being derived from resource specifications of individual resources per-container. A Pod is `Guaranteed` iff all its containers have limits == requests for all the first-class resources (cpu, memory as of now). A Pod is `BestEffort` iff requests & limits are not specified for any resource across all containers. A Pod is `Burstable` otherwise. Note: Existing pods might be more susceptible to OOM Kills on the node due to this PR! To protect pods from being OOM killed on the node, set `limits` for all resources across all containers in a pod. <!-- Reviewable:start --> --- This change is [<img src="http://reviewable.k8s.io/review_button.svg" height="35" align="absmiddle" alt="Reviewable"/>](http://reviewable.k8s.io/reviews/kubernetes/kubernetes/14943) <!-- Reviewable:end -->
parents 45514f72 a64fe657
...@@ -42,7 +42,7 @@ The purpose of filtering the nodes is to filter out the nodes that do not meet c ...@@ -42,7 +42,7 @@ The purpose of filtering the nodes is to filter out the nodes that do not meet c
- `NoDiskConflict`: Evaluate if a pod can fit due to the volumes it requests, and those that are already mounted. - `NoDiskConflict`: Evaluate if a pod can fit due to the volumes it requests, and those that are already mounted.
- `NoVolumeZoneConflict`: Evaluate if the volumes a pod requests are available on the node, given the Zone restrictions. - `NoVolumeZoneConflict`: Evaluate if the volumes a pod requests are available on the node, given the Zone restrictions.
- `PodFitsResources`: Check if the free resource (CPU and Memory) meets the requirement of the Pod. The free resource is measured by the capacity minus the sum of requests of all Pods on the node. To learn more about the resource QoS in Kubernetes, please check [QoS proposal](../proposals/resource-qos.md). - `PodFitsResources`: Check if the free resource (CPU and Memory) meets the requirement of the Pod. The free resource is measured by the capacity minus the sum of requests of all Pods on the node. To learn more about the resource QoS in Kubernetes, please check [QoS proposal](../design/resource-qos.md).
- `PodFitsHostPorts`: Check if any HostPort required by the Pod is already occupied on the node. - `PodFitsHostPorts`: Check if any HostPort required by the Pod is already occupied on the node.
- `HostName`: Filter out all nodes except the one specified in the PodSpec's NodeName field. - `HostName`: Filter out all nodes except the one specified in the PodSpec's NodeName field.
- `MatchNodeSelector`: Check if the labels of the node match the labels specified in the Pod's `nodeSelector` field and, as of Kubernetes v1.2, also match the `scheduler.alpha.kubernetes.io/affinity` pod annotation if present. See [here](../user-guide/node-selection/) for more details on both. - `MatchNodeSelector`: Check if the labels of the node match the labels specified in the Pod's `nodeSelector` field and, as of Kubernetes v1.2, also match the `scheduler.alpha.kubernetes.io/affinity` pod annotation if present. See [here](../user-guide/node-selection/) for more details on both.
......
...@@ -39,11 +39,10 @@ and set them before the container is run. This document describes design of the ...@@ -39,11 +39,10 @@ and set them before the container is run. This document describes design of the
## Motivation ## Motivation
Since we want to make Kubernetes as simple as possible for its users we don’t want to require setting Since we want to make Kubernetes as simple as possible for its users we don’t want to require setting [Resources](../design/resource-qos.md) for container by its owner.
[Resources](resource-qos.md#resource-specifications) On the other hand having Resources filled is critical for scheduling decisions.
for container by its owner. On the other hand having Resources filled is critical for scheduling decisions. Current solution to set up Resources to hardcoded value has obvious drawbacks.
Current solution to set up Resources to hardcoded value has obvious drawbacks. We need to implement a component We need to implement a component which will set initial Resources to a reasonable value.
which will set initial Resources to a reasonable value.
## Design ## Design
...@@ -51,11 +50,9 @@ InitialResources component will be implemented as an [admission plugin](../../pl ...@@ -51,11 +50,9 @@ InitialResources component will be implemented as an [admission plugin](../../pl
[LimitRanger](https://github.com/kubernetes/kubernetes/blob/7c9bbef96ed7f2a192a1318aa312919b861aee00/cluster/gce/config-default.sh#L91). [LimitRanger](https://github.com/kubernetes/kubernetes/blob/7c9bbef96ed7f2a192a1318aa312919b861aee00/cluster/gce/config-default.sh#L91).
For every container without Resources specified it will try to predict amount of resources that should be sufficient for it. For every container without Resources specified it will try to predict amount of resources that should be sufficient for it.
So that a pod without specified resources will be treated as So that a pod without specified resources will be treated as
[Burstable](resource-qos.md#qos-classes). .
InitialResources will set only [request](resource-qos.md#resource-specifications) InitialResources will set only [request](../design/resource-qos.md#requests-and-limits) (independently for each resource type: cpu, memory) field in the first version to avoid killing containers due to OOM (however the container still may be killed if exceeds requested resources).
(independently for each resource type: cpu, memory)
field in the first version to avoid killing containers due to OOM (however the container still may be killed if exceeds requested resources).
To make the component work with LimitRanger the estimated value will be capped by min and max possible values if defined. To make the component work with LimitRanger the estimated value will be capped by min and max possible values if defined.
It will prevent from situation when the pod is rejected due to too low or too high estimation. It will prevent from situation when the pod is rejected due to too low or too high estimation.
......
...@@ -72,9 +72,9 @@ hack/test-update-storage-objects.sh: local storage_versions=${1:-""} ...@@ -72,9 +72,9 @@ hack/test-update-storage-objects.sh: local storage_versions=${1:-""}
hack/test-update-storage-objects.sh: source_file=${test_data[0]} hack/test-update-storage-objects.sh: source_file=${test_data[0]}
hack/test-update-storage-objects.sh:# source_file,resource,namespace,name,old_version,new_version hack/test-update-storage-objects.sh:# source_file,resource,namespace,name,old_version,new_version
pkg/kubelet/network/hairpin/hairpin.go: hairpinModeRelativePath = "hairpin_mode" pkg/kubelet/network/hairpin/hairpin.go: hairpinModeRelativePath = "hairpin_mode"
pkg/kubelet/qos/memory_policy_test.go: t.Errorf("oom_score_adj should be between %d and %d, but was %d", test.lowOOMScoreAdj, test.highOOMScoreAdj, oomScoreAdj) pkg/kubelet/qos/policy_test.go: t.Errorf("oom_score_adj should be between %d and %d, but was %d", test.lowOOMScoreAdj, test.highOOMScoreAdj, oomScoreAdj)
pkg/kubelet/qos/memory_policy_test.go: highOOMScoreAdj int // The min oom_score_adj score the container should be assigned. pkg/kubelet/qos/policy_test.go: highOOMScoreAdj int // The min oom_score_adj score the container should be assigned.
pkg/kubelet/qos/memory_policy_test.go: lowOOMScoreAdj int // The max oom_score_adj score the container should be assigned. pkg/kubelet/qos/policy_test.go: lowOOMScoreAdj int // The max oom_score_adj score the container should be assigned.
pkg/util/oom/oom_linux.go: return fmt.Errorf("invalid PID %d specified for oom_score_adj", pid) pkg/util/oom/oom_linux.go: return fmt.Errorf("invalid PID %d specified for oom_score_adj", pid)
pkg/util/oom/oom_linux.go: oomScoreAdjPath := path.Join("/proc", pidStr, "oom_score_adj") pkg/util/oom/oom_linux.go: oomScoreAdjPath := path.Join("/proc", pidStr, "oom_score_adj")
pkg/util/oom/oom_linux.go:// Writes 'value' to /proc/<pid>/oom_score_adj for all processes in cgroup cgroupName. pkg/util/oom/oom_linux.go:// Writes 'value' to /proc/<pid>/oom_score_adj for all processes in cgroup cgroupName.
......
...@@ -1412,7 +1412,7 @@ func containerAndPodFromLabels(inspect *dockertypes.ContainerJSON) (pod *api.Pod ...@@ -1412,7 +1412,7 @@ func containerAndPodFromLabels(inspect *dockertypes.ContainerJSON) (pod *api.Pod
return return
} }
func (dm *DockerManager) applyOOMScoreAdj(container *api.Container, containerInfo *dockertypes.ContainerJSON) error { func (dm *DockerManager) applyOOMScoreAdj(pod *api.Pod, container *api.Container, containerInfo *dockertypes.ContainerJSON) error {
if containerInfo.State.Pid == 0 { if containerInfo.State.Pid == 0 {
// Container exited. We cannot do anything about it. Ignore this error. // Container exited. We cannot do anything about it. Ignore this error.
glog.V(2).Infof("Failed to apply OOM score adj on container %q with ID %q. Init process does not exist.", containerInfo.Name, containerInfo.ID) glog.V(2).Infof("Failed to apply OOM score adj on container %q with ID %q. Init process does not exist.", containerInfo.Name, containerInfo.ID)
...@@ -1428,7 +1428,7 @@ func (dm *DockerManager) applyOOMScoreAdj(container *api.Container, containerInf ...@@ -1428,7 +1428,7 @@ func (dm *DockerManager) applyOOMScoreAdj(container *api.Container, containerInf
} }
return err return err
} }
oomScoreAdj := dm.calculateOomScoreAdj(container) oomScoreAdj := dm.calculateOomScoreAdj(pod, container)
if err = dm.oomAdjuster.ApplyOOMScoreAdjContainer(cgroupName, oomScoreAdj, 5); err != nil { if err = dm.oomAdjuster.ApplyOOMScoreAdjContainer(cgroupName, oomScoreAdj, 5); err != nil {
if err == os.ErrNotExist { if err == os.ErrNotExist {
// Container exited. We cannot do anything about it. Ignore this error. // Container exited. We cannot do anything about it. Ignore this error.
...@@ -1464,7 +1464,7 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe ...@@ -1464,7 +1464,7 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe
utsMode = namespaceModeHost utsMode = namespaceModeHost
} }
oomScoreAdj := dm.calculateOomScoreAdj(container) oomScoreAdj := dm.calculateOomScoreAdj(pod, container)
id, err := dm.runContainer(pod, container, opts, ref, netMode, ipcMode, utsMode, pidMode, restartCount, oomScoreAdj) id, err := dm.runContainer(pod, container, opts, ref, netMode, ipcMode, utsMode, pidMode, restartCount, oomScoreAdj)
if err != nil { if err != nil {
...@@ -1503,7 +1503,7 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe ...@@ -1503,7 +1503,7 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe
// Check if current docker version is higher than 1.10. Otherwise, we have to apply OOMScoreAdj instead of using docker API. // Check if current docker version is higher than 1.10. Otherwise, we have to apply OOMScoreAdj instead of using docker API.
// TODO: Remove this logic after we stop supporting docker version < 1.10. // TODO: Remove this logic after we stop supporting docker version < 1.10.
if err := dm.applyOOMScoreAdjIfNeeded(container, containerInfo); err != nil { if err = dm.applyOOMScoreAdjIfNeeded(pod, container, containerInfo); err != nil {
return kubecontainer.ContainerID{}, err return kubecontainer.ContainerID{}, err
} }
...@@ -1521,7 +1521,7 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe ...@@ -1521,7 +1521,7 @@ func (dm *DockerManager) runContainerInPod(pod *api.Pod, container *api.Containe
return id, err return id, err
} }
func (dm *DockerManager) applyOOMScoreAdjIfNeeded(container *api.Container, containerInfo *dockertypes.ContainerJSON) error { func (dm *DockerManager) applyOOMScoreAdjIfNeeded(pod *api.Pod, container *api.Container, containerInfo *dockertypes.ContainerJSON) error {
// Compare current API version with expected api version. // Compare current API version with expected api version.
result, err := dm.checkDockerAPIVersion(dockerv110APIVersion) result, err := dm.checkDockerAPIVersion(dockerv110APIVersion)
if err != nil { if err != nil {
...@@ -1529,7 +1529,7 @@ func (dm *DockerManager) applyOOMScoreAdjIfNeeded(container *api.Container, cont ...@@ -1529,7 +1529,7 @@ func (dm *DockerManager) applyOOMScoreAdjIfNeeded(container *api.Container, cont
} }
// If current api version is older than OOMScoreAdj requested, use the old way. // If current api version is older than OOMScoreAdj requested, use the old way.
if result < 0 { if result < 0 {
if err := dm.applyOOMScoreAdj(container, containerInfo); err != nil { if err := dm.applyOOMScoreAdj(pod, container, containerInfo); err != nil {
return fmt.Errorf("Failed to apply oom-score-adj to container %q- %v", err, containerInfo.Name) return fmt.Errorf("Failed to apply oom-score-adj to container %q- %v", err, containerInfo.Name)
} }
} }
...@@ -1537,7 +1537,7 @@ func (dm *DockerManager) applyOOMScoreAdjIfNeeded(container *api.Container, cont ...@@ -1537,7 +1537,7 @@ func (dm *DockerManager) applyOOMScoreAdjIfNeeded(container *api.Container, cont
return nil return nil
} }
func (dm *DockerManager) calculateOomScoreAdj(container *api.Container) int { func (dm *DockerManager) calculateOomScoreAdj(pod *api.Pod, container *api.Container) int {
// Set OOM score of the container based on the priority of the container. // Set OOM score of the container based on the priority of the container.
// Processes in lower-priority pods should be killed first if the system runs out of memory. // Processes in lower-priority pods should be killed first if the system runs out of memory.
// The main pod infrastructure container is considered high priority, since if it is killed the // The main pod infrastructure container is considered high priority, since if it is killed the
...@@ -1546,7 +1546,7 @@ func (dm *DockerManager) calculateOomScoreAdj(container *api.Container) int { ...@@ -1546,7 +1546,7 @@ func (dm *DockerManager) calculateOomScoreAdj(container *api.Container) int {
if container.Name == PodInfraContainerName { if container.Name == PodInfraContainerName {
oomScoreAdj = qos.PodInfraOOMAdj oomScoreAdj = qos.PodInfraOOMAdj
} else { } else {
oomScoreAdj = qos.GetContainerOOMScoreAdjust(container, int64(dm.machineInfo.MemoryCapacity)) oomScoreAdj = qos.GetContainerOOMScoreAdjust(pod, container, int64(dm.machineInfo.MemoryCapacity))
} }
......
...@@ -18,58 +18,50 @@ package qos ...@@ -18,58 +18,50 @@ package qos
import ( import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/kubelet/qos/util"
) )
const ( const (
PodInfraOOMAdj int = -999 PodInfraOOMAdj int = -999
KubeletOOMScoreAdj int = -999 KubeletOOMScoreAdj int = -999
KubeProxyOOMScoreAdj int = -999 KubeProxyOOMScoreAdj int = -999
guaranteedOOMScoreAdj int = -998
besteffortOOMScoreAdj int = 1000
) )
// isMemoryBestEffort returns true if the container's memory requirements are best-effort.
func isMemoryBestEffort(container *api.Container) bool {
// A container is memory best-effort if its memory request is unspecified or 0.
// If a request is specified, then the user expects some kind of resource guarantee.
return container.Resources.Requests.Memory().Value() == 0
}
// isMemoryGuaranteed returns true if the container's memory requirements are Guaranteed.
func isMemoryGuaranteed(container *api.Container) bool {
// A container is memory guaranteed if its memory request == memory limit.
// If memory request == memory limit, the user is very confident of resource consumption.
memoryRequest := container.Resources.Requests.Memory()
memoryLimit := container.Resources.Limits.Memory()
return (*memoryRequest).Cmp(*memoryLimit) == 0 && memoryRequest.Value() != 0
}
// GetContainerOOMAdjust returns the amount by which the OOM score of all processes in the // GetContainerOOMAdjust returns the amount by which the OOM score of all processes in the
// container should be adjusted. The OOM score of a process is the percentage of memory it consumes // container should be adjusted.
// The OOM score of a process is the percentage of memory it consumes
// multiplied by 10 (barring exceptional cases) + a configurable quantity which is between -1000 // multiplied by 10 (barring exceptional cases) + a configurable quantity which is between -1000
// and 1000. Containers with higher OOM scores are killed if the system runs out of memory. // and 1000. Containers with higher OOM scores are killed if the system runs out of memory.
// See https://lwn.net/Articles/391222/ for more information. // See https://lwn.net/Articles/391222/ for more information.
func GetContainerOOMScoreAdjust(container *api.Container, memoryCapacity int64) int { func GetContainerOOMScoreAdjust(pod *api.Pod, container *api.Container, memoryCapacity int64) int {
if isMemoryGuaranteed(container) { switch util.GetPodQos(pod) {
// Memory guaranteed containers should be the last to get killed. case util.Guaranteed:
return -999 // Guaranteed containers should be the last to get killed.
} else if isMemoryBestEffort(container) { return guaranteedOOMScoreAdj
// Memory best-effort containers should be the first to be killed. case util.BestEffort:
return 1000 return besteffortOOMScoreAdj
} else { }
// Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally,
// we want to protect Burstable containers that consume less memory than requested. // Burstable containers are a middle tier, between Guaranteed and Best-Effort. Ideally,
// The formula below is a heuristic. A container requesting for 10% of a system's // we want to protect Burstable containers that consume less memory than requested.
// memory will have an oom score adjust of 900. If a process in container Y // The formula below is a heuristic. A container requesting for 10% of a system's
// uses over 10% of memory, its OOM score will be 1000. The idea is that containers // memory will have an OOM score adjust of 900. If a process in container Y
// which use more than their request will have an OOM score of 1000 and will be prime // uses over 10% of memory, its OOM score will be 1000. The idea is that containers
// targets for OOM kills. // which use more than their request will have an OOM score of 1000 and will be prime
// Note that this is a heuristic, it won't work if a container has many small processes. // targets for OOM kills.
memoryRequest := container.Resources.Requests.Memory().Value() // Note that this is a heuristic, it won't work if a container has many small processes.
oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity memoryRequest := container.Resources.Requests.Memory().Value()
// A memory guaranteed container using 100% of memory can have an OOM score of 1. Ensure oomScoreAdjust := 1000 - (1000*memoryRequest)/memoryCapacity
// that memory burstable containers have a higher OOM score. // A guaranteed pod using 100% of memory can have an OOM score of 1. Ensure
if oomScoreAdjust < 2 { // that burstable pods have a higher OOM score adjustment.
return 2 if oomScoreAdjust < 2 {
} return 2
return int(oomScoreAdjust) }
// Give burstable pods a higher chance of survival over besteffort pods.
if int(oomScoreAdjust) == besteffortOOMScoreAdj {
return int(oomScoreAdjust - 1)
} }
return int(oomScoreAdjust)
} }
...@@ -29,157 +29,168 @@ const ( ...@@ -29,157 +29,168 @@ const (
) )
var ( var (
zeroRequestMemoryBestEffort = api.Container{ cpuLimit = api.Pod{
Resources: api.ResourceRequirements{ Spec: api.PodSpec{
Requests: api.ResourceList{ Containers: []api.Container{
api.ResourceName(api.ResourceCPU): resource.MustParse("5m"), {
api.ResourceName(api.ResourceMemory): resource.MustParse("0G"), Resources: api.ResourceRequirements{
}, Limits: api.ResourceList{
Limits: api.ResourceList{ api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
api.ResourceName(api.ResourceCPU): resource.MustParse("5m"), },
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), },
},
}, },
}, },
} }
edgeMemoryBestEffort = api.Container{ memoryLimitCPURequest = api.Pod{
Resources: api.ResourceRequirements{ Spec: api.PodSpec{
Requests: api.ResourceList{ Containers: []api.Container{
api.ResourceName(api.ResourceMemory): resource.MustParse("0G"), {
}, Resources: api.ResourceRequirements{
Limits: api.ResourceList{ Requests: api.ResourceList{
api.ResourceName(api.ResourceMemory): resource.MustParse("0G"), api.ResourceName(api.ResourceCPU): resource.MustParse("0"),
},
Limits: api.ResourceList{
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
},
},
},
}, },
}, },
} }
noRequestMemoryBestEffort = api.Container{ zeroMemoryLimit = api.Pod{
Resources: api.ResourceRequirements{ Spec: api.PodSpec{
Limits: api.ResourceList{ Containers: []api.Container{
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), {
Resources: api.ResourceRequirements{
Limits: api.ResourceList{
api.ResourceName(api.ResourceMemory): resource.MustParse("0"),
},
},
},
}, },
}, },
} }
noLimitMemoryBestEffort = api.Container{} noRequestLimit = api.Pod{
Spec: api.PodSpec{
memoryGuaranteed = api.Container{ Containers: []api.Container{
Resources: api.ResourceRequirements{ {
Requests: api.ResourceList{ Resources: api.ResourceRequirements{},
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), },
},
Limits: api.ResourceList{
api.ResourceName(api.ResourceCPU): resource.MustParse("5m"),
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
}, },
}, },
} }
memoryBurstable = api.Container{ equalRequestLimitCPUMemory = api.Pod{
Resources: api.ResourceRequirements{ Spec: api.PodSpec{
Requests: api.ResourceList{ Containers: []api.Container{
api.ResourceName(api.ResourceMemory): resource.MustParse(strconv.Itoa(standardMemoryAmount / 2)), {
}, Resources: api.ResourceRequirements{
Limits: api.ResourceList{ Requests: api.ResourceList{
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"), api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
api.ResourceName(api.ResourceCPU): resource.MustParse("5m"),
},
Limits: api.ResourceList{
api.ResourceName(api.ResourceCPU): resource.MustParse("5m"),
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
},
},
},
}, },
}, },
} }
memoryBurstableNoLimit = api.Container{ cpuUnlimitedMemoryLimitedWithRequests = api.Pod{
Resources: api.ResourceRequirements{ Spec: api.PodSpec{
Requests: api.ResourceList{ Containers: []api.Container{
api.ResourceName(api.ResourceMemory): resource.MustParse(strconv.Itoa(standardMemoryAmount - 1)), {
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceName(api.ResourceMemory): resource.MustParse(strconv.Itoa(standardMemoryAmount / 2)),
api.ResourceName(api.ResourceCPU): resource.MustParse("5m"),
},
Limits: api.ResourceList{
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
},
},
},
}, },
}, },
} }
)
func TestIsMemoryBestEffort(t *testing.T) {
validCases := []api.Container{zeroRequestMemoryBestEffort, noRequestMemoryBestEffort, noLimitMemoryBestEffort, edgeMemoryBestEffort}
for _, container := range validCases {
if !isMemoryBestEffort(&container) {
t.Errorf("container %+v is memory best-effort", container)
}
}
invalidCases := []api.Container{memoryGuaranteed, memoryBurstable}
for _, container := range invalidCases {
if isMemoryBestEffort(&container) {
t.Errorf("container %+v is not memory best-effort", container)
}
}
}
func TestIsMemoryGuaranteed(t *testing.T) { requestNoLimit = api.Pod{
validCases := []api.Container{memoryGuaranteed} Spec: api.PodSpec{
for _, container := range validCases { Containers: []api.Container{
if !isMemoryGuaranteed(&container) { {
t.Errorf("container %+v is memory guaranteed", container) Resources: api.ResourceRequirements{
} Requests: api.ResourceList{
} api.ResourceName(api.ResourceMemory): resource.MustParse(strconv.Itoa(standardMemoryAmount - 1)),
invalidCases := []api.Container{zeroRequestMemoryBestEffort, noRequestMemoryBestEffort, noLimitMemoryBestEffort, edgeMemoryBestEffort, memoryBurstable} api.ResourceName(api.ResourceCPU): resource.MustParse("5m"),
for _, container := range invalidCases { },
if isMemoryGuaranteed(&container) { },
t.Errorf("container %+v is not memory guaranteed", container) },
} },
},
} }
} )
type oomTest struct { type oomTest struct {
container *api.Container pod *api.Pod
memoryCapacity int64 memoryCapacity int64
lowOOMScoreAdj int // The max oom_score_adj score the container should be assigned. lowOOMScoreAdj int // The max oom_score_adj score the container should be assigned.
highOOMScoreAdj int // The min oom_score_adj score the container should be assigned. highOOMScoreAdj int // The min oom_score_adj score the container should be assigned.
} }
func TestGetContainerOOMScoreAdjust(t *testing.T) { func TestGetContainerOOMScoreAdjust(t *testing.T) {
oomTests := []oomTest{ oomTests := []oomTest{
{ {
container: &zeroRequestMemoryBestEffort, pod: &cpuLimit,
memoryCapacity: 4000000000, memoryCapacity: 4000000000,
lowOOMScoreAdj: 1000, lowOOMScoreAdj: 999,
highOOMScoreAdj: 1000, highOOMScoreAdj: 999,
}, },
{ {
container: &edgeMemoryBestEffort, pod: &memoryLimitCPURequest,
memoryCapacity: 8000000000, memoryCapacity: 8000000000,
lowOOMScoreAdj: 1000, lowOOMScoreAdj: 999,
highOOMScoreAdj: 1000, highOOMScoreAdj: 999,
}, },
{ {
container: &noRequestMemoryBestEffort, pod: &zeroMemoryLimit,
memoryCapacity: 7230457451, memoryCapacity: 7230457451,
lowOOMScoreAdj: 1000, lowOOMScoreAdj: 1000,
highOOMScoreAdj: 1000, highOOMScoreAdj: 1000,
}, },
{ {
container: &noLimitMemoryBestEffort, pod: &noRequestLimit,
memoryCapacity: 4000000000, memoryCapacity: 4000000000,
lowOOMScoreAdj: 1000, lowOOMScoreAdj: 1000,
highOOMScoreAdj: 1000, highOOMScoreAdj: 1000,
}, },
{ {
container: &memoryGuaranteed, pod: &equalRequestLimitCPUMemory,
memoryCapacity: 123456789, memoryCapacity: 123456789,
lowOOMScoreAdj: -999, lowOOMScoreAdj: -998,
highOOMScoreAdj: -999, highOOMScoreAdj: -998,
}, },
{ {
container: &memoryBurstable, pod: &cpuUnlimitedMemoryLimitedWithRequests,
memoryCapacity: standardMemoryAmount, memoryCapacity: standardMemoryAmount,
lowOOMScoreAdj: 495, lowOOMScoreAdj: 495,
highOOMScoreAdj: 505, highOOMScoreAdj: 505,
}, },
{ {
container: &memoryBurstableNoLimit, pod: &requestNoLimit,
memoryCapacity: standardMemoryAmount, memoryCapacity: standardMemoryAmount,
lowOOMScoreAdj: 2, lowOOMScoreAdj: 2,
highOOMScoreAdj: 2, highOOMScoreAdj: 2,
}, },
} }
for _, test := range oomTests { for _, test := range oomTests {
oomScoreAdj := GetContainerOOMScoreAdjust(test.container, test.memoryCapacity) oomScoreAdj := GetContainerOOMScoreAdjust(test.pod, &test.pod.Spec.Containers[0], test.memoryCapacity)
if oomScoreAdj < test.lowOOMScoreAdj || oomScoreAdj > test.highOOMScoreAdj { if oomScoreAdj < test.lowOOMScoreAdj || oomScoreAdj > test.highOOMScoreAdj {
t.Errorf("oom_score_adj should be between %d and %d, but was %d", test.lowOOMScoreAdj, test.highOOMScoreAdj, oomScoreAdj) t.Errorf("oom_score_adj should be between %d and %d, but was %d", test.lowOOMScoreAdj, test.highOOMScoreAdj, oomScoreAdj)
} }
......
...@@ -18,7 +18,7 @@ package util ...@@ -18,7 +18,7 @@ package util
import ( import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/api/resource"
) )
const ( const (
...@@ -48,22 +48,61 @@ func isResourceBestEffort(container *api.Container, resource api.ResourceName) b ...@@ -48,22 +48,61 @@ func isResourceBestEffort(container *api.Container, resource api.ResourceName) b
} }
// GetPodQos returns the QoS class of a pod. // GetPodQos returns the QoS class of a pod.
// The QoS class of a pod is the lowest QoS class for each resource in each container. // A pod is besteffort if none of its containers have specified any requests or limits.
// A pod is guaranteed only when requests and limits are specified for all the containers and they are equal.
// A pod is burstable if limits and requests do not match across all containers.
func GetPodQos(pod *api.Pod) string { func GetPodQos(pod *api.Pod) string {
qosValues := sets.NewString() requests := api.ResourceList{}
limits := api.ResourceList{}
zeroQuantity := resource.MustParse("0")
isGuaranteed := true
for _, container := range pod.Spec.Containers { for _, container := range pod.Spec.Containers {
qosPerResource := GetQoS(&container) // process requests
for _, qosValue := range qosPerResource { for name, quantity := range container.Resources.Requests {
qosValues.Insert(qosValue) if quantity.Cmp(zeroQuantity) == 1 {
delta := quantity.Copy()
if _, exists := requests[name]; !exists {
requests[name] = *delta
} else {
delta.Add(requests[name])
requests[name] = *delta
}
}
}
// process limits
for name, quantity := range container.Resources.Limits {
if quantity.Cmp(zeroQuantity) == 1 {
delta := quantity.Copy()
if _, exists := limits[name]; !exists {
limits[name] = *delta
} else {
delta.Add(limits[name])
limits[name] = *delta
}
}
}
if len(container.Resources.Limits) != len(supportedComputeResources) {
isGuaranteed = false
} }
} }
if qosValues.Has(BestEffort) { if len(requests) == 0 && len(limits) == 0 {
return BestEffort return BestEffort
} }
if qosValues.Has(Burstable) { // Check is requests match limits for all resources.
return Burstable if isGuaranteed {
for name, req := range requests {
if lim, exists := limits[name]; !exists || lim.Cmp(req) != 0 {
isGuaranteed = false
break
}
}
}
if isGuaranteed &&
len(requests) == len(limits) &&
len(limits) == len(supportedComputeResources) {
return Guaranteed
} }
return Guaranteed return Burstable
} }
// GetQos returns a mapping of resource name to QoS class of a container // GetQos returns a mapping of resource name to QoS class of a container
......
...@@ -65,35 +65,63 @@ func TestGetPodQos(t *testing.T) { ...@@ -65,35 +65,63 @@ func TestGetPodQos(t *testing.T) {
expected string expected string
}{ }{
{ {
pod: newPod("guaranteed", []api.Container{
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
}),
expected: Guaranteed,
},
{
pod: newPod("guaranteed-guaranteed", []api.Container{
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
}),
expected: Guaranteed,
},
{
pod: newPod("best-effort-best-effort", []api.Container{
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
}),
expected: BestEffort,
},
{
pod: newPod("best-effort", []api.Container{ pod: newPod("best-effort", []api.Container{
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")), newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
}), }),
expected: BestEffort, expected: BestEffort,
}, },
{ {
pod: newPod("best-effort-burstable", []api.Container{
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
newContainer("burstable", getResourceList("1", ""), getResourceList("2", "")),
}),
expected: Burstable,
},
{
pod: newPod("best-effort-guaranteed", []api.Container{ pod: newPod("best-effort-guaranteed", []api.Container{
newContainer("best-effort", getResourceList("", ""), getResourceList("", "")), newContainer("best-effort", getResourceList("", ""), getResourceList("", "")),
newContainer("guaranteed", getResourceList("10m", "100Mi"), getResourceList("10m", "100Mi")), newContainer("guaranteed", getResourceList("10m", "100Mi"), getResourceList("10m", "100Mi")),
}), }),
expected: BestEffort, expected: Burstable,
}, },
{ {
pod: newPod("best-effort-cpu-guaranteed-memory", []api.Container{ pod: newPod("burstable-cpu-guaranteed-memory", []api.Container{
newContainer("best-effort", getResourceList("", "100Mi"), getResourceList("", "100Mi")), newContainer("burstable", getResourceList("", "100Mi"), getResourceList("", "100Mi")),
}), }),
expected: BestEffort, expected: Burstable,
}, },
{ {
pod: newPod("burstable", []api.Container{ pod: newPod("burstable-guaranteed", []api.Container{
newContainer("burstable", getResourceList("10m", "100Mi"), getResourceList("100m", "200Mi")), newContainer("burstable", getResourceList("1", "100Mi"), getResourceList("2", "100Mi")),
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
}), }),
expected: Burstable, expected: Burstable,
}, },
{ {
pod: newPod("guaranteed", []api.Container{ pod: newPod("burstable", []api.Container{
newContainer("guaranteed", getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")), newContainer("burstable", getResourceList("10m", "100Mi"), getResourceList("100m", "200Mi")),
}), }),
expected: Guaranteed, expected: Burstable,
}, },
} }
for _, testCase := range testCases { for _, testCase := range testCases {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment