Summary
The ECS agent forces the Docker's CpuShares setting to always be set for every container it runs, even if it's empty on the task definition. This negatively affects performance of containers that use this information to configure their threading.
Description
Programming languages may be "container-aware" and use cgroup information available inside a container, like memory limits or CPU shares, to make configuration decisions at runtime.
Java 10, 11 and recent builds of Java 8 (including Amazon Corretto) use Docker memory limits to configure the heap and CPU shares to set the amount of available CPUs for threading and garbage collection. See: https://blog.docker.com/2018/04/improved-docker-container-integration-with-java-10/
When leaving the CPU shares unset on a task definition, the ECS agent will always set "2" as the value passed to Docker to create the container. This makes Java services running in the container think it has only 1 CPU available (shares <1024) and will configure its threading to use a maximum of 1 CPU, greatly affecting performance.
Expected Behavior
When leaving CPU shares unset on a task definition, CpuShares is 0 and Java's Runtime.getRuntime().availableProcessors() returns the amount of CPUs of the host.
Observed Behavior
When leaving CPU shares unset, CpuShares is 2 and Runtime.getRuntime().availableProcessors() is always 1.
Example
A one-liner to quickly test with the openjdk:8 or openjdk:11 images:
sh -c 'echo "class Main { public static void main(String[] args) { System.out.println(Runtime.getRuntime().availableProcessors()); } }" > Main.java; javac Main.java; java Main'
Summary
The ECS agent forces the Docker's CpuShares setting to always be set for every container it runs, even if it's empty on the task definition. This negatively affects performance of containers that use this information to configure their threading.
Description
Programming languages may be "container-aware" and use cgroup information available inside a container, like memory limits or CPU shares, to make configuration decisions at runtime.
Java 10, 11 and recent builds of Java 8 (including Amazon Corretto) use Docker memory limits to configure the heap and CPU shares to set the amount of available CPUs for threading and garbage collection. See: https://blog.docker.com/2018/04/improved-docker-container-integration-with-java-10/
When leaving the CPU shares unset on a task definition, the ECS agent will always set "2" as the value passed to Docker to create the container. This makes Java services running in the container think it has only 1 CPU available (shares <1024) and will configure its threading to use a maximum of 1 CPU, greatly affecting performance.
Expected Behavior
When leaving CPU shares unset on a task definition, CpuShares is 0 and Java's
Runtime.getRuntime().availableProcessors()returns the amount of CPUs of the host.Observed Behavior
When leaving CPU shares unset, CpuShares is 2 and
Runtime.getRuntime().availableProcessors()is always 1.Example
A one-liner to quickly test with the
openjdk:8oropenjdk:11images: