Kahibaro
Discord Login Register

12.3 Resource Limits

Why Resource Limits Matter

Containers share the host machine kernel and, by default, can use as much CPU and memory as the host will give them. For small experiments this might not be a problem. In real projects, especially when running many containers on the same host, you must control how much CPU, memory, and other resources each container can consume.

Without limits, a single misbehaving container can exhaust memory or CPU, make the host slow or unresponsive, and indirectly affect other services. Resource limits give you a way to keep containers within safe boundaries so the whole system remains stable and predictable.

Always treat resource limits as part of your application configuration, not as an afterthought. Unbounded containers can impact the stability of the entire host.

CPU Limits

Docker lets you control how much CPU time a container can use relative to other containers. You do not control which physical core is used, but you can limit how much of the total CPU capacity the container can access.

A common pattern is to use the --cpus flag when you start a container with docker run. This flag represents an amount of CPU time relative to a single core. For example, --cpus=1.0 means the container can use up to one full core worth of CPU time, --cpus=0.5 means up to half a core, and --cpus=2.5 means two and a half cores if the host has that much capacity.

If your host has 4 cores and you run two containers, one with --cpus=3 and another with no limit, the first container can use up to the equivalent of 3 cores. The second container will compete for the remaining capacity. In practice, the kernel scheduler tries to share CPU time but the explicit limit on the first container stops it from using more than you allowed.

Another control is --cpuset-cpus, which lets you pin a container to specific cores, using numbers like 0,1 or 0-2. This does not change how much CPU time it can use, but it restricts which cores it can run on. This can be useful for performance tuning or isolating workloads.

Use --cpus to cap total CPU share and --cpuset-cpus to choose which cores a container may use. Do not assume CPU limits control latency, they only control how much CPU time is available.

Memory Limits

Unlike CPU, memory is not as forgiving. If a container uses more memory than the host can provide, the kernel may kill processes to protect the system. To avoid this, you should explicitly cap container memory.

When you start a container you can set --memory to define the maximum memory the container can use. For example, --memory=512m limits the container to 512 megabytes, and --memory=2g limits it to 2 gigabytes. If the container tries to allocate more than this, it will fail memory allocations or the kernel will terminate the container with an out of memory error.

You can also set --memory-reservation to express a soft limit. The soft limit is a threshold where the kernel starts preferring to reclaim memory from that container under pressure, but it is allowed to go above the reservation up to the hard --memory limit if memory is available.

A common pattern is to set both: for example, --memory-reservation=512m and --memory=1g. This tells Docker that the container should usually stay around 512 megabytes, but it can grow as high as 1 gigabyte when the system has free memory. If many containers want to exceed their reservations at the same time, the kernel will be stricter and your hard limit protects the host.

Never set only an unrealistically high --memory or leave it unlimited in production. Choose a hard limit that your application can work within and test it under load to avoid unexpected out of memory kills.

Swap and Memory Behavior

On hosts that use swap, memory usage has an extra dimension. Swap lets the kernel move rarely used pages from RAM to disk. This can prevent kills at the cost of higher latency.

Docker lets you control swap use with --memory-swap. The value of --memory-swap sets the total virtual memory (RAM plus swap) available to the container. If you set --memory=1g and --memory-swap=2g, the container can use 1 gigabyte of real memory and 1 additional gigabyte in swap.

If you set --memory and do not set --memory-swap, Docker applies a default that makes total virtual memory twice the value of --memory. If you set --memory-swap equal to --memory, swap is effectively disabled for that container.

Choosing whether to allow swap is a trade off. Allowing swap can make the system more tolerant of brief spikes, but may introduce slowdowns if the container touches swapped pages frequently. Disabling swap can keep latency more predictable, but increases the chance of processes being killed when memory is exhausted.

For latency sensitive containers, consider setting --memory-swap equal to --memory to avoid unpredictable delays from swapping. For batch or background work, allowing swap can improve stability at the cost of speed.

Limiting I/O

Disk and block device I/O can also become a bottleneck. If one container reads or writes heavily, it can slow down others. Docker supports throttling I/O using flags like --device-read-bps, --device-write-bps, --device-read-iops, and --device-write-iops with docker run.

These options are more advanced and depend on you specifying the correct device path, for example /dev/sda. They are typically used on dedicated hosts or when tuning performance for specific storage devices. In many development setups they are not configured at all, but for production systems with heavy disk workloads, setting I/O limits can prevent a single container from monopolizing disk bandwidth.

You can also limit how much data a container can write to its writable layer indirectly, by using external storage systems and volume configurations, but that belongs to broader storage design and not only to resource limits.

If your application performs intensive disk operations, test it under concurrent load with I/O limits, otherwise one container can saturate the disk and impact every other workload on the host.

Setting Limits with `docker run`

For a single container, resource limits are usually set directly on the docker run command line. You combine the flags described earlier to create a resource profile that matches your application needs.

For example, consider a web service that should not use more than one core and 512 megabytes of memory under normal conditions, with no swap, and that must not expand silently. You might run it with:

docker run --cpus=1.0 --memory=512m --memory-swap=512m image-name

If you have a background worker that can tolerate slowdowns but might need bursts of CPU and extra memory occasionally, you could be more permissive:

docker run --cpus=2.0 --memory=1g --memory-swap=2g image-name

The flags you choose should align with measurements of real usage, not guesses. In practice, you start with a conservative limit, observe the container under expected load, and then adjust. You will see memory usage, CPU usage, and performance, and refine your limits based on data.

Resource flags on docker run are enforceable constraints. If you set them too low, the container can become unstable or slow. Always validate limits under realistic traffic or workload before using them in production.

Coordinating Limits Across Multiple Containers

On a single host that runs many containers, you must think about the sum of all limits relative to the physical machine. If you have a host with 8 gigabytes of RAM and you set ten containers each with --memory=2g, it is possible for them all to try to use the full allocation, which will exceed the host capacity and cause pressure or failures.

Resource limits are not reservations of hardware, they are caps. The sum of the caps can exceed the host capacity. It is your responsibility to keep the overall configuration realistic. A safe approach is to keep the sum of memory limits below physical memory, with some headroom for the host system, and to choose CPU limits that reflect how much parallel work you truly need.

In many orchestrated environments, resource requests and limits are coordinated by the platform. On a single Docker host without orchestration, you must do that planning yourself. It can help to maintain a simple table that lists each container and its limits, and compare the total against the host.

Do not assume that Docker prevents you from overcommitting the host. Limits are per container, not a global safety net. Always plan total resource use at the host level.

Monitoring and Adjusting Resource Limits

After you define resource limits, monitoring is essential. Basic tools such as docker stats show live CPU, memory, network, and I/O usage per container. You can see whether containers consistently approach their memory cap, or how much CPU time they consume.

If a container regularly runs close to its memory limit, you have two main options. You can optimize the application to use less memory, or you can raise the limit if the host capacity allows. If it only spikes occasionally, you might adjust swap behavior instead, or tune garbage collection or cache sizes inside the application.

When a container regularly hits its CPU limit, it will often respond more slowly. That can be acceptable for low priority workloads, but not for user facing services. In that case you can increase --cpus or consider scaling the application horizontally by running more container instances and distributing the traffic.

Over time, you will refine a pattern of limits that match the natural resource profile of each service. Treat these numbers as living configuration that evolves as your software changes.

Use real metrics from tools like docker stats and external monitoring to guide changes to resource limits. Adjusting limits blindly can hide problems or create new ones.

Summary of Core Principles

Resource limits are about protecting the host and other containers from a single runaway process, and about making performance predictable. CPU limits control how much processing time a container can receive. Memory limits control how much RAM it may allocate before the kernel intervenes. Swap and I/O limits refine behavior during pressure and heavy disk usage.

When you apply limits thoughtfully, and keep measuring how containers behave, you can pack more workloads on the same hardware without sacrificing reliability. Resource limits are a fundamental part of any serious Docker based deployment strategy, and should be defined, tested, and maintained with the same care as any other key configuration.

Views: 7

Comments

Please login to add a comment.

Don't have an account? Register now!