Author: Buddha Prakash (@dubstack), Vishnu Kannan (@vishh)
Last Updated: 06/23/2016
Status: Draft Proposal (WIP)
This document proposes a design for introducing pod level resource accounting to Kubernetes, and outlines the implementation and rollout plan.
As of now Quality of Service(QoS) is not enforced at a pod level. Excepting pod evictions, all the other QoS features are not applicable at the pod level. To better support QoS, there is a need to add support for pod level resource accounting in Kubernetes.
We propose to have a unified cgroup hierarchy with pod level cgroups for better resource management. We will have a cgroup hierarchy with top level cgroups for the three QoS classes Guaranteed, Burstable and BestEffort. Pods (and their containers) belonging to a QoS class will be grouped under these top level QoS cgroups. And all containers in a pod are nested under the pod cgroup.
The proposed cgroup hierarchy would allow for more efficient resource management and lead to improvements in node reliability. This would also allow for significant latency optimizations in terms of pod eviction on nodes with the use of pod level resource usage metrics. This document provides a basic outline of how we plan to implement and rollout this feature.
Kubernetes currently supports container level isolation only and lets users specify resource requests/limits on the containers Compute Resources. The kubelet
creates a cgroup sandbox (via it's container runtime) for each container.
There are a few shortcomings to the current model.
High level requirements for the design are as follows:
How we intend to achieve these high level goals is covered in greater detail in the Implementation Plan.
We use the following denotations in the sections below:
For the three QoS classes
G⇒ Guaranteed QoS, Bu⇒ Burstable QoS, BE⇒ BestEffort QoS
For the value specified for the --qos-memory-overcommitment flag
qmo⇒ qos-memory-overcommitment
Currently the Kubelet highly prioritizes resource utilization and thus allows BE pods to use as much resources as they want. And in case of OOM the BE pods are first to be killed. We follow this policy as G pods often don't use the amount of resource request they specify. By overcommiting the node the BE pods are able to utilize these left over resources. And in case of OOM the BE pods are evicted by the eviciton manager. But there is some latency involved in the pod eviction process which can be a cause of concern in latency-sensitive servers. On such servers we would want to avoid OOM conditions on the node. Pod level cgroups allow us to restrict the amount of available resources to the BE pods. So reserving the requested resources for the G and Bu pods would allow us to avoid invoking the OOM killer.
We add a flag qos-memory-overcommitment
to kubelet which would allow users to configure the percentage of memory overcommitment on the node. We have the default as 100, so by default we allow complete overcommitment on the node and let the BE pod use as much memory as it wants, and not reserve any resources for the G and Bu pods. As expected if there is an OOM in such a case we first kill the BE pods before the G and Bu pods.
On the other hand if a user wants to ensure very predictable tail latency for latency-sensitive servers he would need to set qos-memory-overcommitment to a really low value(preferrably 0). In this case memory resources would be reserved for the G and Bu pods and BE pods would be able to use only the left over memory resource.
Examples in the next section.
For the initial implementation we will only support limits for cpu and memory resources.
A pod can belong to one of the following 3 QoS classes: Guaranteed, Burstable, and BestEffort, in decreasing order of priority.
G
pods will be placed at the $Root
cgroup by default. $Root
is the system root ie. "/" by default and if --cgroup-root
flag is used then we use the specified cgroup-root as the $Root
. To ensure Kubelet's idempotent behaviour we follow a pod cgroup naming format which is opaque and deterministic. Say we have a pod with UID: 5f9b19c9-3a30-11e6-8eea-28d2444e470d
the pod cgroup PodUID would be named: pod-5f9b19c93a3011e6-8eea28d2444e470d
.
Note: The cgroup-root flag would allow the user to configure the root of the QoS cgroup hierarchy. Hence cgroup-root would be redefined as the root of QoS cgroup hierarchy and not containers.
/PodUID/cpu.quota = cpu limit of Pod
/PodUID/cpu.shares = cpu request of Pod
/PodUID/memory.limit_in_bytes = memory limit of Pod
Example: We have two pods Pod1 and Pod2 having Pod Spec given below
kind: Pod
metadata:
name: Pod1
spec:
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
memory: 2Gi
kind: Pod
metadata:
name: Pod2
spec:
containers:
name: foo
resources:
limits:
cpu: 20m
memory: 2Gii
Pod1 and Pod2 are both classified as G
and are nested under the Root
cgroup.
/Pod1/cpu.quota = 110m
/Pod1/cpu.shares = 110m
/Pod2/cpu.quota = 20m
/Pod2/cpu.shares = 20m
/Pod1/memory.limit_in_bytes = 3Gi
/Pod2/memory.limit_in_bytes = 2Gi
We have the following resource parameters for the Bu
cgroup.
/Bu/cpu.shares = summation of cpu requests of all Bu pods
/Bu/PodUID/cpu.quota = Pod Cpu Limit
/Bu/PodUID/cpu.shares = Pod Cpu Request
/Bu/memory.limit_in_bytes = Allocatable - {(summation of memory requests/limits of `G` pods)*(1-qom/100)}
/Bu/PodUID/memory.limit_in_bytes = Pod memory limit
Note: For the
BuQoS when limits are not specified for any one of the containers, the Pod limit defaults to the node resource allocatable quantity.
Example: We have two pods Pod3 and Pod4 having Pod Spec given below:
kind: Pod
metadata:
name: Pod3
spec:
containers:
name: foo
resources:
limits:
cpu: 50m
memory: 2Gi
requests:
cpu: 20m
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
memory: 1Gi
kind: Pod
metadata:
name: Pod4
spec:
containers:
name: foo
resources:
limits:
cpu: 20m
memory: 2Gi
requests:
cpu: 10m
memory: 1Gi
Pod3 and Pod4 are both classified as Bu
and are hence nested under the Bu cgroup
And for qom
= 0
/Bu/cpu.shares = 30m
/Bu/Pod3/cpu.quota = 150m
/Bu/Pod3/cpu.shares = 20m
/Bu/Pod4/cpu.quota = 20m
/Bu/Pod4/cpu.shares = 10m
/Bu/memory.limit_in_bytes = Allocatable - 5Gi
/Bu/Pod3/memory.limit_in_bytes = 3Gi
/Bu/Pod4/memory.limit_in_bytes = 2Gi
For pods belonging to the BE
QoS we don't set any quota.
/BE/cpu.shares = 2
/BE/cpu.quota= not set
/BE/memory.limit_in_bytes = Allocatable - {(summation of memory requests of all `G` and `Bu` pods)*(1-qom/100)}
/BE/PodUID/memory.limit_in_bytes = no limit
Example: We have a pod 'Pod5' having Pod Spec given below:
kind: Pod
metadata:
name: Pod5
spec:
containers:
name: foo
resources:
name: bar
resources:
Pod5 is classified as BE
and is hence nested under the BE cgroup
And for qom
= 0
/BE/cpu.shares = 2
/BE/cpu.quota= not set
/BE/memory.limit_in_bytes = Allocatable - 7Gi
/BE/Pod5/memory.limit_in_bytes = no limit
In systemd we have slices for the three top level QoS class. Further each pod is a subslice of exactly one of the three QoS slices. Each container in a pod belongs to a scope nested under the qosclass-pod slice.
Example: We plan to have the following cgroup hierarchy on systemd systems
/memory/G-PodUID.slice/containerUID.scope
/cpu,cpuacct/G-PodUID.slice/containerUID.scope
/memory/Bu.slice/Bu-PodUID.slice/containerUID.scope
/cpu,cpuacct/Bu.slice/Bu-PodUID.slice/containerUID.scope
/memory/BE.slice/BE-PodUID.slice/containerUID.scope
/cpu,cpuacct/BE.slice/BE-PodUID.slice/containerUID.scope
--cgroup-root
is specified then the specified cgroup-root is used as "$Root".Bu
and BE
QoS classes.G
QoS class. G
pod cgroups are brought up directly under the Root
cgroup.kube-reserved cgroup contains the kubelet specific daemons.
$ROOT
|
+- Pod1
| |
| +- Container1
| +- Container2
| ...
+- Pod2
| +- Container3
| ...
+- ...
|
+- Bu
| |
| +- Pod3
| | |
| | +- Container4
| | ...
| +- Pod4
| | +- Container5
| | ...
| +- ...
|
+- BE
| |
| +- Pod5
| | |
| | +- Container6
| | +- Container7
| | ...
| +- ...
|
+- System-reserved
| |
| +- system
| +- docker (optional)
| +- ...
|
+- Kube-reserved
| |
| +- kubelet
| +- docker (optional)
| +- ...
|
G
over Bu
and BE
pods.G
class, the hierarchy allows the G
pods to burst and utilize all of Node's Allocatable capacity.BE
and Bu
pods are strictly restricted from bursting and hogging resources and thus G
Pods are guaranteed resource isolation.BE
pods are treated as lowest priority. So for the BE
QoS cgroup we set cpu shares to the lowest possible value ie.2. This ensures that the BE
containers get a relatively small share of cpu time.BE
pods can use any amount of free resources on the node.BE
cgroup as (Allocatable - summation of memory requests of G
and Bu
pods) would result in BE
pods becoming more susceptible to being OOM killed. As more G
and Bu
pods are scheduled kubelet will more likely kill BE
pods, even if the G
and Bu
pods are using less than their request since we will be dynamically reducing the size of BE
m.limit_in_bytes. But this allows for better memory guarantees to the G
and Bu
pods.The implementation plan is outlined in the next sections. We will have a 'cgroups-per-qos' flag to specify if the user wants to use the QoS based cgroup hierarchy. The flag would be set to false by default at least in v1.4.
Two top level cgroups for Bu
and BE
QoS classes are created when Kubelet starts to run on a node. All G
pods cgroups are by default nested under the Root
. So we dont create a top level cgroup for the G
class. For raw cgroup systems we would use libcontainers cgroups manager for general cgroup management(cgroup creation/destruction). But for systemd we don't have equivalent support for slice management in libcontainer yet. So we will be adding support for the same in the Kubelet. These cgroups are only created once on Kubelet initialization as a part of node setup. Also on systemd these cgroups are transient units and will not survive reboot.
Have docker manager create container cgroups under pod level cgroups. With the docker runtime, we will pass --cgroup-parent using the syntax expected for the corresponding cgroup-driver the runtime was configured to use.
We want to have rkt create pods under a root QoS class that kubelet specifies, and set pod level cgroup parameters mentioned in this proposal by itself.
Update Kubelet’s metrics provider to include Pod level metrics. Use cAdvisor's cgroup subsystem information to determine various Pod level usage metrics.
Note: Changes to cAdvisor might be necessary.
This feature will be opt-in in v1.4 and an opt-out in v1.5. We recommend users to drain their nodes and opt-in, before switching to v1.5, which will result in a no-op when v1.5 kubelet is rolled out.
The implementation goals of the first milestone are outlined below.
Other smaller work items that we would be good to have before the release of this feature.
/
then set node resource allocatable as the cgroup resource limits on cgroup root.To better support our requirements we needed to make some changes/add features to Libcontainer as well