Containers themselves are light, but by default a container has access to all the CPU resources the Docker host kernel scheduler will allow.
Internally Docker uses cgroups to limit CPU resources, and this is exposed as the flag “–cpus” when bringing up a docker container:
sudo docker run -it --cpus=1.0 alpine:latest /bin/sh
This will limit the CPU abilities of this container to the equivalent of a single CPU core on the Docker host system, balanced among the Docker host processors.
Monitoring host CPU with htop
A nice way to watch CPU utilization is using htop on the host system. This is typically installed already, but if you need to install it.
sudo apt-get install htop -y
Here is a screenshot showing the 4 CPU Docker host system at baseline, with no discernible load.
Go ahead and keep htop running in one console to prepare for the next section.
Running container and stress-ng
stress-ng is a utility that can stress a system in multiple ways, but we will be using it only for CPU here.
Instead of having you install stress-ng on the alpine image every time you run a test, I created an alpine-stressng-cpu Dockerfile in github. To use this project:
# get project from github git clone https://github.com/fabianlee/alpine-stressng-cpu.git cd alpine-stressng-cpu # make utility is a prereq sudo apt-get install make -f # build docker image make docker-build # run quick test (50% load on all host CPU for 20 seconds) make docker-run
We did not place any limits on container CPU, so notice that while the docker-run was executing, htop should show all your host CPU at ~50% utilization.
Limiting container CPU
Now we need to start exploring the “–cpu” flag that will limit a container’s processing power relative to the processing power of a single host CPU.
For example, if a single host CPU can execute 100 instructions/second, then –cpus=0.15 means the container will be capable of 15 instructions/second, and –cpus=0.75 means the container will be capable of 75 instructions/second.
EXAMPLE 1 – 1 CPU at 100% load
sudo docker run -it --cpus=1.0 -e cpuload=100 -e timeout=20 fabianlee/alpine-stressng-cpu:1.0.0
We requested that the equivalent power of 1 host CPU be put at 100% load. Since we have 4 host CPU, that placed 25% load on each as shown below.
EXAMPLE 2 – 2 CPU at 50% load
sudo docker run -it --cpus=2.0 -e cpuload=50 -e timeout=20 fabianlee/alpine-stressng-cpu:1.0.0
We requested that the equivalent power of 2 host CPU be put at 50% load. Since we have 4 host CPU, that placed 25% load on each as shown below.
Notice that in both examples so far the host CPU load on all processors has been ~25%. This is because 1 CPU at 100% matches the host CPU processing power of 2 CPU at 50%.
Pinning to host CPU
In the previous section we had container CPU load spread across all host CPU. This is the recommended way of sharing processing load.
But if you do need to pin processing to a set of host CPU, that is possible using the flag “–cpuset-cpus”.
The example below requests the equivalent of 2 host CPU at 100% load, pinned to CPU 0 and 1.
sudo docker run -it --cpus=2.0 --cpuset-cpus=0,1 -e cpuload=100 -e timeout=20 fabianlee/alpine-stressng-cpu:1.0.0
As you can see CPU 0 and 1 are ~100%, while 3 and 4 are almost unused.
REFERENCES
docker, runtime constraints on resources
docker, container has access to all host resources
linuxhint, cgroups and cpu.cfs_period_us and cpu.cfs_quota_us
stackoverflow, definitions of cpu.cfs_period_us and cpu.fs_quota_us
stackoverflow, flags cpus and cpuset-cpus
tecmint, describing usage of stress-ng
github, ctop utility. Good for memory/network, doesn’t handle multiple core well
NOTES
Looking at cgroup controls for CPU
$ sudo docker run -it --cpus=0.25 alpine-stressng /bin/sh # cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us 100000 # cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us 25000 # cat /sys/fs/cgroup/cpuset/cpuset.cpus 0-3 # exit
You would see the cfs_period_us=100000, while cfs_quota_us=25000. Which shows the fractional amount of host CPU requested. If you had used “–cpus=1.0”, both numbers would be 100000.