fix: Respect container memory and CPU limits from cgroups - #2128
fix: Respect container memory and CPU limits from cgroups#2128Mantisus wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR makes Crawlee’s CPU and memory metrics container-aware on Linux by reading cgroup v1/v2 limits/usage (instead of relying solely on host-wide /proc/meminfo / psutil), so the autoscaler sizes budgets and CPU utilization against the resources actually available to the running process.
Changes:
- Add a new cgroup discovery + metrics reader (
src/crawlee/_utils/cgroup.py) that resolves controllers via/proc/self/mountinfo+/proc/self/cgroupand reads the tightest applicable limits. - Update
get_memory_info()/get_cpu_info()to prefer cgroup-scoped totals/usage when limits apply, falling back to host metrics otherwise. - Add comprehensive unit tests with a fake cgroup filesystem, and document autoscaling behavior under resource limits.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/unit/_utils/test_cgroup.py | Adds unit tests covering cgroup v1/v2 discovery and limit/usage semantics, plus system.py integration behavior. |
| src/crawlee/_utils/system.py | Switches CPU/memory “system-wide” metrics to cgroup-aware readings when applicable; keeps host fallbacks. |
| src/crawlee/_utils/cgroup.py | Implements cgroup controller discovery and reads memory/cpu limits + usage from the most relevant hierarchy levels. |
| docs/guides/scaling_crawlers.mdx | Documents how autoscaling respects cgroup CPU/memory limits when running under container/systemd constraints. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
If I am correct, all the tests run against a fake cgroup filesystem. Is there a way to cover this end-to-end? e.g., running a crawler in a container with |
That's right. To add the test, we'll probably need to add a new job to CI. With my permissions in the repository, I won't be able to test it properly.
Yes, I tested it locally with (crawlee) mantisus@Kleimor:~/repos/apify/crawlee-python$ docker run --rm $MOUNTS $IMG $RUN
configuration
available_memory_ratio : 0.25
max_used_memory_ratio : 0.9
max_used_cpu_ratio : 0.95
memory_mbytes : None
system_info_interval : 0:00:01
limits read from the cgroup
memory : None
cpu quota : None
cpu set : 20
latest memory snapshot
current_size : 60.27 MB
max_memory_size : 4.88 GB
system_wide_used_size : 2.63 GB
system_wide_memory_size : 19.53 GB
is_overloaded : False
latest cpu snapshot
used_ratio : 0.216
max_used_ratio : 0.95
is_overloaded : False
system status over the last 6 cpu samples
cpu : 0.00 of 0.40 allowed ok
memory : 0.00 of 0.20 allowed ok
event_loop : 0.00 of 0.60 allowed ok
client : 0.00 of 0.30 allowed ok
is_system_idle: True(crawlee) mantisus@Kleimor:~/repos/apify/crawlee-python$ docker run --rm -m 512m --cpus 1 $MOUNTS $IMG $RUN
configuration
available_memory_ratio : 0.25
max_used_memory_ratio : 0.9
max_used_cpu_ratio : 0.95
memory_mbytes : None
system_info_interval : 0:00:01
limits read from the cgroup
memory : MemoryLimit(limit=536870912, working_set=84348928)
cpu quota : 1.0
cpu set : 20
latest memory snapshot
current_size : 60.19 MB
max_memory_size : 128.00 MB
system_wide_used_size : 80.98 MB
system_wide_memory_size : 512.00 MB
is_overloaded : False
latest cpu snapshot
used_ratio : 0.996
max_used_ratio : 0.95
is_overloaded : True
system status over the last 6 cpu samples
cpu : 0.74 of 0.40 allowed OVERLOADED
memory : 0.00 of 0.20 allowed ok
event_loop : 0.00 of 0.60 allowed ok
client : 0.00 of 0.30 allowed ok
is_system_idle: False(crawlee) mantisus@Kleimor:~/repos/apify/crawlee-python$ docker run --rm -m 512m --cpus 6 $MOUNTS $IMG $RUN
configuration
available_memory_ratio : 0.25
max_used_memory_ratio : 0.9
max_used_cpu_ratio : 0.95
memory_mbytes : None
system_info_interval : 0:00:01
limits read from the cgroup
memory : MemoryLimit(limit=536870912, working_set=85512192)
cpu quota : 6.0
cpu set : 20
latest memory snapshot
current_size : 60.32 MB
max_memory_size : 128.00 MB
system_wide_used_size : 82.48 MB
system_wide_memory_size : 512.00 MB
is_overloaded : False
latest cpu snapshot
used_ratio : 0.723
max_used_ratio : 0.95
is_overloaded : False
system status over the last 6 cpu samples
cpu : 0.00 of 0.40 allowed ok
memory : 0.00 of 0.20 allowed ok
event_loop : 0.00 of 0.60 allowed ok
client : 0.00 of 0.30 allowed ok
is_system_idle: True(crawlee) mantisus@Kleimor:~/repos/apify/crawlee-python$ docker run --rm -m 512m --cgroupns=host $MOUNTS $IMG $RUN
configuration
available_memory_ratio : 0.25
max_used_memory_ratio : 0.9
max_used_cpu_ratio : 0.95
memory_mbytes : None
system_info_interval : 0:00:01
limits read from the cgroup
memory : MemoryLimit(limit=536870912, working_set=89137152)
cpu quota : None
cpu set : 20
latest memory snapshot
current_size : 60.34 MB
max_memory_size : 128.00 MB
system_wide_used_size : 85.98 MB
system_wide_memory_size : 512.00 MB
is_overloaded : False
latest cpu snapshot
used_ratio : 0.200
max_used_ratio : 0.95
is_overloaded : False
system status over the last 6 cpu samples
cpu : 0.00 of 0.40 allowed ok
memory : 0.00 of 0.20 allowed ok
event_loop : 0.00 of 0.60 allowed ok
client : 0.00 of 0.30 allowed ok
is_system_idle: True |
Description
get_memory_infoandget_cpu_inforeported the host machine even when the crawler ran under a container limit, because/proc/meminfois not namespaced. The autoscaler sized its budget from host RAM and kept scaling until the container got killed, and a container pinned to two cores read the load of the whole machine as idle. Both now come from the cgroup of the process, with nothing to configure.Two decisions worth a look:
/proc/self/mountinfoand/proc/self/cgroupinstead of assuming/sys/fs/cgroup, which is what makes this work under--cgroupns=host.Without a limit, and outside Linux, the host values are used as before. A crawler in a container whose limit is below the host RAM now gets a smaller
max_memory_size, so its concurrency drops.Issues
Testing