CPU limits

Just like in our previous section about memory limits for services, docker run also supports a variety of CPU settings and parameters to tweak the computational needs of your services:

  • -c/--cpu-shares: On a high-load host, all tasks are weighted equally by default. Setting this on a task or service (from the default of 1024) will increase or decrease the percentage of CPU utilization that the task can be scheduled for.
  • --cpu-quota: This flag sets the number of microseconds that a task or service can use the CPU within a default block of time of 100 milliseconds (100,000 microseconds). For example, to only allow a maximum of 50% of a single CPU's core usage to a task, you would set this flag to 50000. For multiple cores, you would need to increase this value accordingly.
  • --cpu-period: This changes the previous quota flag default interval in microseconds over which the cpu-quota is being evaluated (100 milliseconds/100,000 microseconds) and either reduces it or increases it to inversely affect the CPU resource allocation to a service.
  • --cpus: A float value that combines parts of both cpu-quota and cpu-period to limit the number of CPU core allocations to the task. For example, if you only want a task to use up to a quarter of a single CPU resource, you would set this to 0.25 and it would have the same effect as --cpu-quota 25000 --cpu-period 100000.
  • --cpuset-cpus: This array flag allows the service to only run on specified CPUs indexed from 0. If you wanted a service to use only CPUs 0 and 3, you could use --cpuset-cpus "0,3". This flag also supports entering values as a range (that is 1-3).

While it might seem like a lot of options to consider, in most cases you will only need to tweak the --cpu-shares and --cpus flags, but it is possible that you will need much more granular control over the resources that they provide.

How about we see what the --cpu-shares value can do for us? For this, we need to simulate resource contention and in the next example, we will try to do this by incrementing an integer variable as many times as we can within a period of 60 seconds in as many containers as there are CPUs on the machine. The code is a bit gnarly, but most of it is to get the CPU to reach resource contention levels on all cores.

Add the following to a file called cpu_shares.sh (also available on https://github.com/sgnn7/deploying_with_docker):

#!/bin/bash -e

CPU_COUNT=$(nproc --all)
START_AT=$(date +%s)
STOP_AT=$(( $START_AT + 60 ))

echo "Detected $CPU_COUNT CPUs"
echo "Time range: $START_AT -> $STOP_AT"

declare -a CONTAINERS

echo "Allocating all cores but one with default shares"
for ((i = 0; i < $CPU_COUNT - 1; i++)); do
  echo "Starting container $i"
  CONTAINERS[i]=$(docker run 
                  -d 
                  ubuntu 
                  /bin/bash -c "c=0; while [ $STOP_AT -gt $(date +%s) ]; do c=$((c + 1)); done; echo $c")
done

echo "Starting container with high shares"
  fast_task=$(docker run 
              -d 
              --cpu-shares 8192 
              ubuntu 
              /bin/bash -c "c=0; while [ $STOP_AT -gt $(date +%s) ]; do c=$((c + 1)); done; echo $c")

  CONTAINERS[$((CPU_COUNT - 1))]=$fast_task

echo "Waiting full minute for containers to finish..."
sleep 62

for ((i = 0; i < $CPU_COUNT; i++)); do
  container_id=${CONTAINERS[i]}
  echo "Container $i counted to $(docker logs $container_id)"
  docker rm $container_id >/dev/null
done

Now we will run this code and see the effects of our flag:

$ # Make the file executable
$ chmod +x ./cpu_shares.sh

$ # Run our little program
$ ./cpu_shares.sh
Detected 8 CPUs
Time range: 1507405189 -> 1507405249
Allocating all cores but one with default shares
Starting container 0
Starting container 1
Starting container 2
Starting container 3
Starting container 4
Starting container 5
Starting container 6
Starting container with high shares
Waiting full minute for containers to finish...
Container 0 counted to 25380
Container 1 counted to 25173
Container 2 counted to 24961
Container 3 counted to 24882
Container 4 counted to 24649
Container 5 counted to 24306
Container 6 counted to 24280
Container 7 counted to 31938

While the container with the high --cpu-share value didn't get the full increase in count that might have been expected, if we ran the benchmark over a longer period of time with a tighter CPU-bound loop, the difference would be much more drastic. But even in our small example you can see that the last container had a distinct advantage over all the other running containers on the machine.

To see how the --cpus flag compares, let's take a look at what it can do on an uncontended system:

$ # First without any limiting
$ time docker run -it
--rm
ubuntu
/bin/bash -c 'for ((i=0; i<100; i++)); do sha256sum /bin/bash >/dev/null; done'
real 0m1.902s
user 0m0.030s
sys 0m0.006s

$ # Now with only a quarter of the CPU available
$ time docker run -it
--rm
--cpus=0.25
ubuntu
/bin/bash -c 'for ((i=0; i<100; i++)); do sha256sum /bin/bash >/dev/null; done'
real 0m6.456s
user 0m0.018s
sys 0m0.017s

As you can see, the --cpus flag is really good for ensuring that a task will not use any more CPU than the specified value even if there is no contention for resources on the machine.

Keep in mind that there are a few more options for limiting resource usage for containers that are a bit outside of the scope of the general ones that we have covered already, but they are mainly for device-specific limitations (such as device IOPS). If you are interested in seeing all of the available ways to limit resources to a task or a service, you should be able to find them all at https://docs.docker.com/engine/reference/run/#runtime-constraints-on-resources.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.248.149