×
Community Blog Kubernetes : Assign CPU Resource Defaults and Limits to Containers

Kubernetes : Assign CPU Resource Defaults and Limits to Containers

In this tutorial, we will explore how to declare and limit CPU resources according to the requirements of your Kubernetes Pods.

By Alwyn Botha, Alibaba Cloud Tech Share Author. Tech Share is Alibaba Cloud's incentive program to encourage the sharing of technical knowledge and best practices within the cloud community.

This tutorial demonstrates the following :

  • How to declare the CPU resource requirements of your Pods
  • How Kubernetes administrators define default CPU resource requirements
  • How Kubernetes administrators define CPU resource usage limits

CPU resource usage limits are needed to prevent any one Pod from totally consuming ALL the CPU time on one node ( server running Kubernetes and capable of hosting Pods ). You can use this functionality in development to prevent developers from accidentally hogging all CPU resources.

You can use this functionality in production - to ensure no accidental runaway process in one Pod from hogging all CPU resources - preventing everything else from doing productive work.

This tutorial will cover the following topics:

  • Pod declares CPU request and limits
  • Pod defines using half a CPU - Millicores syntax
  • Pod defines using half a CPU - fractional syntax
  • Pod declares no CPU request and limits
  • CPU LimitRanges
  • LimitRange with defaults and min, max values
  • Pod that does not adhere to CPU limits
  • LimitRanges and Namespaces

1) Pod Declares CPU Request and Limits

Pods can declare how much CPU they need.

See example below:

  • Pod declares it needs 1 full CPU at maximum
  • Pod declares it needs half a CPU at minimum

CPU resource limits are defines using two syntaxes:

  • 1, 2, 2.5 ... defines 1, 2 and 2.5 CPUs
  • 1000m, 2500m, 150m ... defines 1 CPU, 2.5 CPUs and 0.150 CPUs

The second syntax uses Millicores. 1000m equals one CPU on all computers.

( One Millicores is 1/1000 of a CPU, therefore 1000m equals 1 CPU )

A four core server has a CPU capacity of 4000m.

Millicores syntax is easy to read: 150m versus 0.150. Use the syntax you prefer. Have a standard at your company of which to use.

The Pod below defines it needs 500m minimum and 1 CPU maximum.

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
    
    resources:
      limits:
        cpu: "1"
      requests:
        cpu: 500m
        
  restartPolicy: Never

Create the Pod.

kubectl create -f myBench-Pod.yaml 
   
pod/mybench-pod created
   

We now need to test how those CPU limits are enforced.

We do that using sysbench - a Linux benchmark utility.

Syntax:

sysbench --threads=1 --cpu-max-prime=8005005 --verbosity=0 cpu run

  • --threads=1 ... run using 1 thread
  • --cpu-max-prime=8005005 ... calculation of prime numbers up to this value
  • --verbosity=0 ... do not show details while doing the run
  • cpu run ... run the CPU run benchmark included in sysbench

( I randomly played with cpu-max-prime value till I got one that takes around 15 seconds to run. That is enough time to switch to the other console and do a screen capture of the top command output. )

We defined that our Pod needs 1 full CPU to do its work. We can test that using sysbench inside our Pod.

Use kubectl exec to ssh into the Pod. There we can run sysbench on the shown Linux Pod shell prompt.

kubectl exec mybench-pod -i -t -- /bin/bash

Throughout this tutorial enter the sysbench command as shown in these output blocks.

sysbench --threads=1 --cpu-max-prime=8005005 --verbosity=0 cpu run

Tasks: 203 total,   1 running, 202 sleeping,   0 stopped,   0 zombie
%Cpu0  :  0.0 us,  0.0 sy,  0.0 ni,100.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.0 us,  1.7 sy,  0.0 ni, 98.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :100.0 us,  0.0 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  1.7 sy,  0.0 ni, 98.3 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

  PID USER      PR  NI    VIRT    RES  %CPU  %MEM     TIME+ S COMMAND
31866 root      20   0   87.8m   5.1m 100.0   0.3   0:03.37 S sysbench --threads=1 --cpu-max-prime=8005005 --verbosity+

As expected, our Pod uses 1 full CPU at 100%. You can see that in the 4 individual core detail lines as well in the process detail line of our thread.

In the test below we let sysbench run 2 threads.

How to filter top command output:

  • press letter o ... this starts its filter functionality
  • in our specific case: enter COMMAND=sysbench and press enter
  • only sysbench processes shown
  • press l to hide the load average line
  • press m to hide the memory and swap line.
sysbench --threads=2 --cpu-max-prime=8005005 --verbosity=0 cpu run

Tasks: 203 total,   1 running, 202 sleeping,   0 stopped,   0 zombie
%Cpu0  : 51.4 us,  0.0 sy,  0.0 ni, 48.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 50.0 us,  0.0 sy,  0.0 ni, 50.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  1.4 us,  0.0 sy,  0.0 ni, 98.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.0 us,  0.0 sy,  0.0 ni, 98.6 id,  0.0 wa,  0.0 hi,  1.4 si,  0.0 st

  PID USER      PR  NI    VIRT    RES  %CPU  %MEM     TIME+ S COMMAND
32589 root      20   0   87.9m   5.1m 102.7   0.3   0:03.61 S sysbench --threads=2 --cpu-max-prime=8005005 --verbosity+

As expected, our Pod uses 1 full CPU overall at 100%.

You can see that in the 4 individual core detail lines that each thread runs on its own CPU - each using 50% CPU - to keep total usage at 100%

You will see that more clearly in tests below using 3 and 4 threads.

Test using 3 threads:

sysbench --threads=3 --cpu-max-prime=8005005 --verbosity=0 cpu run

Tasks: 203 total,   1 running, 202 sleeping,   0 stopped,   0 zombie
%Cpu0  : 32.5 us,  0.9 sy,  0.0 ni, 66.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 32.2 us,  0.0 sy,  0.0 ni, 67.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 32.2 us,  0.9 sy,  0.0 ni, 67.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  0.9 us,  0.9 sy,  0.0 ni, 98.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

  PID USER      PR  NI    VIRT    RES  %CPU  %MEM     TIME+ S COMMAND
  335 root      20   0   88.0m   5.0m  97.4   0.3   0:08.41 S sysbench --threads=3 --cpu-max-prime=8005005 --verbosity+

As expected, our Pod still uses 1 full CPU overall at 100%.

You can see that in the 4 individual core detail lines that each of the 3 threads run on its own CPU - each using 33% CPU - to keep total usage at 100%

sysbench --threads=4 --cpu-max-prime=8005005 --verbosity=0 cpu run

Tasks: 203 total,   1 running, 202 sleeping,   0 stopped,   0 zombie
%Cpu0  : 25.5 us,  0.9 sy,  0.0 ni, 73.6 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 26.1 us,  0.9 sy,  0.0 ni, 72.1 id,  0.0 wa,  0.0 hi,  0.9 si,  0.0 st
%Cpu2  : 26.6 us,  0.9 sy,  0.0 ni, 72.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 26.4 us,  0.9 sy,  0.0 ni, 71.8 id,  0.0 wa,  0.0 hi,  0.9 si,  0.0 st

  PID USER      PR  NI    VIRT    RES  %CPU  %MEM     TIME+ S COMMAND
  678 root      20   0   88.0m   5.1m 100.9   0.3   0:05.22 S sysbench --threads=4 --cpu-max-prime=8005005 --verbosity+

As expected, our Pod still uses 1 full CPU overall at 100%.

You can see that in the 4 individual core detail lines that each of the 4 threads run on its own CPU - each using 25% CPU - to keep total usage at 100%

Basic demo completed, delete Pod.

kubectl delete -f myBench-Pod.yaml --force --grace-period=0

pod "mybench-pod" force deleted

I use --force --grace-period=0 to delete Pod immediately. Do not use in production. By default Pods get 30 seconds to do their shutdown routines ( after receiving delete command ).

2) Pod Defines Using Half a CPU - Millicores Syntax

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
    
    resources:
      limits:
        cpu: "500m"
      requests:
        cpu: 500m
        
  restartPolicy: Never

Note CPU limit is 500m = half a CPU.

Create the Pod.

kubectl create -f myBench-Pod.yaml 
   
pod/mybench-pod created
   

Exec into Pod:

kubectl exec mybench-pod -i -t -- /bin/bash

Run benchmark.

sysbench --threads=1 --cpu-max-prime=8005005 --verbosity=0 cpu run

Tasks: 208 total,   1 running, 207 sleeping,   0 stopped,   0 zombie
%Cpu0  :  1.1 us,  1.1 sy,  0.0 ni, 97.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 38.6 us,  1.1 sy,  0.0 ni, 60.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  : 11.5 us,  0.0 sy,  0.0 ni, 88.5 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  :  1.1 us,  0.0 sy,  0.0 ni, 98.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

  PID USER      PR  NI    VIRT    RES  %CPU  %MEM     TIME+ S COMMAND
 4210 root      20   0   87.8m   5.1m  50.0   0.3   0:02.55 S sysbench --threads=1 --cpu-max-prime=8005005 --verbosity+

Pod got limited to half a CPU - works as expected.

Demo completed, delete Pod.

kubectl delete -f myBench-Pod.yaml --force --grace-period=0

pod "mybench-pod" force deleted

3) Pod Defines Using Half a CPU - Fractional Syntax

Same Pod as before, only using 0.5 to define half a CPU.

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
    
    resources:
      limits:
        cpu: .5
      requests:
        cpu: .5
        
  restartPolicy: Never

Create the Pod.

kubectl create -f myBench-Pod.yaml 
   
pod/mybench-pod created
   

Enter Pod:

kubectl exec mybench-pod -i -t -- /bin/bash

Run sysbench:

sysbench --threads=1 --cpu-max-prime=8005005 --verbosity=0 cpu run

Tasks: 208 total,   1 running, 207 sleeping,   0 stopped,   0 zombie
%Cpu0  :  1.5 us,  1.5 sy,  0.0 ni, 96.9 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  :  0.0 us,  1.6 sy,  0.0 ni, 98.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu2  :  0.0 us,  1.6 sy,  0.0 ni, 98.4 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu3  : 52.3 us,  0.0 sy,  0.0 ni, 47.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st

  PID USER      PR  NI    VIRT    RES  %CPU  %MEM     TIME+ S COMMAND
 5639 root      20   0   87.8m   5.1m  50.8   0.3   0:01.49 S sysbench --threads=1 --cpu-max-prime=8005005 --verbosity+

Identical to previous test ( proving 500m = half a CPU )

Investigate how Kubernetes understands our Pod YAML spec.

( Output truncated - leaving only the good stuff. Rest of tutorial always only show relevant describe output )

kubectl describe pod/mybench-pod

Name:               mybench-pod
Containers:
  mybench-container:
    State:          Running
      Started:      Fri, 11 Jan 2019 09:17:09 +0200
    Ready:          True
    Limits:
      cpu:  500m
    Requests:
      cpu:        500m

Shows Millicores values, not 0.5

kubectl delete -f myBench-Pod.yaml --force --grace-period=0

pod "mybench-pod" force deleted

4) Pod Declares No CPU Request and Limits

Pod below has no reference to limits.

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
            
  restartPolicy: Never      

Create the Pod.

kubectl create -f myBench-Pod.yaml 
   
pod/mybench-pod created
   

Enter Pod and run sysbench test.

kubectl exec mybench-pod -i -t -- /bin/bash

sysbench --threads=4 --cpu-max-prime=8005005 --verbosity=0 cpu run

%Cpu0  : 95.3 us,  4.3 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
%Cpu1  : 97.8 us,  1.8 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
%Cpu2  : 96.8 us,  2.9 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.4 si,  0.0 st
%Cpu3  : 97.8 us,  1.4 sy,  0.0 ni,  0.0 id,  0.0 wa,  0.0 hi,  0.7 si,  0.0 st

  PID USER      PR  NI    VIRT    RES  %CPU  %MEM     TIME+ S COMMAND
 9142 root      20   0   88.0m   5.1m 392.4   0.3   0:33.63 S sysbench --threads=4 --cpu-max-prime=8005005 --verbosity+

Reckless Pod hogs all 4 cores at 100%. This is why you need CPU limits.

Delete this Pod:

kubectl delete -f myBench-Pod.yaml --force --grace-period=0

pod "mybench-pod" force deleted

5) CPU LimitRanges

We need CPU default limits that automatically limits ALL Pods on a node.

LimitRanges fits the bill.

LimitRange below adds default limits ALL Pods that do not declare their own limits.

nano mycpu-limit-range.yaml

apiVersion: v1
kind: LimitRange
metadata:
  name: mycpu-limit-range
spec:
  limits:
  - default:
      cpu: 0.75
    defaultRequest:
      cpu: 0.25
    type: Container

Create the LimitRange.

kubectl create -f mycpu-limit-range.yaml

limitrange/mycpu-limit-range created

We reuse the Pod spec from previous example : no limits in its spec.

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
            
  restartPolicy: Never      

Create the Pod.

kubectl create -f myBench-Pod.yaml 
   
pod/mybench-pod created
   

Describe how Kubernetes executed our desired Pod spec.

kubectl describe pod/mybench-pod

Name:               mybench-pod

Annotations:        kubernetes.io/limit-ranger: LimitRanger plugin set: cpu request for container mybench-container; cpu limit for container mybench-container

Containers:
  mybench-container:
    State:          Running
      Started:      Fri, 11 Jan 2019 09:48:24 +0200
    Ready:          True
    Limits:
      cpu:  750m
    Requests:
      cpu:        250m

Note the annotation :

LimitRanger automatically added a cpu limit for container mybench-container

The limits listed agree with the LimitRange spec.

Best Practice Have LimitRanges at your development shop.

Delete this specific LimitRange.

kubectl delete limits/mycpu-limit-range

limitrange "mycpu-limit-range" deleted

6) LimitRange with Defaults and Min, Max Values

Previously we used LimitRanges defaults.

LimitRanges also define min and max limits. See spec below:

3 examples of new Pod creations follow so you can see how this works. ( My tutorials teach via examples rather than long text descriptions with few demos. )

nano mycpu-limit-range.yaml

apiVersion: v1
kind: LimitRange
metadata:
  name: mycpu-limit-range
spec:
  limits:
  - default:
      cpu: 0.75
    defaultRequest:
      cpu: 0.25

    max:
      cpu: "2000m"
    min:
      cpu: "200m"

    type: Container

Create our LimitRange

kubectl create -f mycpu-limit-range.yaml

Define our Pod.

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
    
    resources:
      limits:
        cpu: "1000m"
      requests:
        cpu: 300m
        
  restartPolicy: Never

Create the Pod.

kubectl create -f myBench-Pod.yaml 
   
pod/mybench-pod created
   

This Pod does not need defaults - it declares its own.

This Pod spec limit fall within the allowed min-max LimitRange defined above . Pod create command went smoothly - nothing else to show here.

Describe Pod shows Pod created exactly as we specified.

kubectl describe pod/mybench-pod

Name:               mybench-pod

Containers:
  mybench-container:
    State:          Running
    Ready:          True
    Limits:
      cpu:  1
    Requests:
      cpu:        300m

7) Pod That Does Not Adhere to CPU Limits

Edit Pod spec.

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
    
    resources:
      limits:
        cpu: "3000m"
      requests:
        cpu: 100m
        
  restartPolicy: Never

Note our CPU request is below LimitRange min

Note our CPU limit is above LimitRange max

Attempt to create the Pod:

kubectl create -f myBench-Pod.yaml

Error from server (Forbidden): error when creating "myBench-Pod.yaml": pods "mybench-pod" is forbidden: [minimum cpu usage per Container is 200m, but request is 100m., maximum cpu usage per Container is 2, but limit is 3.]

Error is as expected.

Modify Pod spec to be within limits.

nano myBench-Pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: mybench-pod
spec:
  containers:
  - name: mybench-container
    image: mytutorials/centos:bench
    imagePullPolicy: IfNotPresent
    
    command: ['sh', '-c', 'echo The CPU Bench Pod is Running ; sleep 3600']
    
    resources:
      limits:
        cpu: "1500m"
      requests:
        cpu: 250m
        
  restartPolicy: Never

Create the Pod.

kubectl create -f myBench-Pod.yaml 
   
pod/mybench-pod created
   

Pod create worked. It falls within min and max limits.

Delete Pod and LimitRange .

kubectl delete -f myBench-Pod.yaml --force --grace-period=0

pod "mybench-pod" force deleted

and ...

kubectl delete limits/mycpu-limit-range

limitrange "mycpu-limit-range" deleted

8) LimitRanges and Namespaces

LimitRanges live in Namespaces

If you are familiar with Namespaces all you need to know is that LimitRanges you create work in the Namespace you are currently in.

You should divide your development server into several Namespaces - per development team. Different teams may need different CPU and RAM limits. That is the purpose of Namespaces : separation of resources visibility based on your needs.

If you do not define any Namespaces and no LimitRanges your server is WIDE open to CPU and RAM abuse - deliberate and accidental.

For more information https://kubernetes.io/docs/tasks/administer-cluster/namespaces-walkthrough/

Conclusion

CPU defaults and limits are easy to understand, so let's put this knowledge to test. Create your own LimitRange and modify your own Pod with:

  • Limit and request
  • Limit and without request
  • No limit and with request

Enter Pod via kubectl exec and run sysbench. Exceed self-declared limits and LimitRange max to learn how all this interacts.

Here are several other exercises for you to try:

  • Using just one Pod, request much more CPU cores than those available on your node.
  • Create 3 Pods that in total request CPU resources that exceed the number of CPU cores on the node
  • Create 3 Pods that in total limit CPU resources that exceed the number of CPU cores on the node
  • Since Pod may have several containers, create a Pod with 3 containers (each with different limits) and investigate how those limits are enforced. For example, if 3 containers in a Pod request 1 CPU each, the total Pod demand is 3 CPUs. You must attempt to run 3 Pods simultaneously if you want to test Pods that collectively exceed node cpu capability.

Hopefully by trying out these exercises, you can accurately predict any behavior for any Pod when given any LimitRange spec / definition.

0 0 0
Share on

Alibaba Clouder

2,599 posts | 762 followers

You may also like

Comments