Properly allocate resources for helper containers (egress proxy, tracing, rclone) in dynamic services - #9397
Conversation
…y-allocate-extra-container-resources
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #9397 +/- ##
==========================================
- Coverage 87.68% 85.26% -2.42%
==========================================
Files 2124 2126 +2
Lines 83922 84039 +117
Branches 1569 1569
==========================================
- Hits 73583 71657 -1926
- Misses 9913 11956 +2043
Partials 426 426
Continue to review full report in Codecov by Harness.
🚀 New features to boost your workflow:
|
…y-allocate-extra-container-resources
…y-allocate-extra-container-resources
pcrespov
left a comment
There was a problem hiding this comment.
I'm surprised this wasn't handled from the start. I assumed accounting for helper container resources would be a core, extensible part of the dynamic-sidecar design (i.e. new "helper" services added later, like rclone, would automatically be factored into the resource calculation).
My mental model was: the director spawns a dy-sidecar, which starts a docker-compose bundle including the core, the "real" helper containers (rclone for volumes/copy, the reverse proxy for the front-end, egress proxy, tracing) and the "main" user service(s), at least one of which exposes a web server the proxy connects to. Since the dy-sidecar is part of the swarm, it should reserve resources for the whole bundle it brings along, not just the main service.
Some open questions before this goes out:
- Tier allocation: we have pre-defined machine tiers (S, M, L). What percentage of each tier is allocated to each component (main service, rclone, reverse proxy, egress proxy, tracing, and whatever comes later)?
- Impact on constrained tiers: if rclone is capped tightly on tier S, does that slow down copying? Does it still work reliably? I'm assuming it has minimal specs, but does this mean the tiers themselves need to be revisited (and by how much)?
I'd want these questions addressed before release to avoid surprises in production.
I highty encourange you to critically review this with @mguidon @Konohana0608 since they are the main service creators and anticipate pbolems!!
| f"{MEM_RESOURCE_LIMIT_KEY}={memory_bytes}", | ||
| ] | ||
| ) | ||
| deploy = service.setdefault("deploy", {}) |
There was a problem hiding this comment.
@GitHK , if you do not re-request for review, probably @sanderegg will never notice your question ...
…y-allocate-extra-container-resources
|
There are currently two resource allocations strategies that can be adopted
I have considered your feedback @pcrespov and @sanderegg and compiled a list of pros and cons for the two viable approaches for construing helper services. I think the second approach is better. |
…y-allocate-extra-container-resources
…y-allocate-extra-container-resources
…y-allocate-extra-container-resources
4cbaea8 to
27a494d
Compare
…y-allocate-extra-container-resources
…y-allocate-extra-container-resources
…y-allocate-extra-container-resources
|
So from my perspective, two things are affected by this:
|
|
@mguidon let me reply here before I forget
We assign the entire footprint (user services + helper containers) to the dynamic-sidecar, from this we remove what is allocated for each container, which means that if the service starts on the machine, there are no more possible surprises regarding resource allocation. Everything is now limited.
I agree with you here that we can cut rclone, especially since we also limit the disk for them to 5GB. |
|
@sanderegg I'm missing one point here that I think I did not manage to get across. When a dynamic service is started, its resources are provided by the catalog. What happens with an autoscaled instance? The question would be also, what would be the ideal way to deal with it if you are a creator. What would you like to see? I guess MAG can answer here. |
Autoscaling does not change whatever is asked from the docker services ever. What it does is:
|
sanderegg
left a comment
There was a problem hiding this comment.
this looks much better. Still some questions on the high overview.
| # (egress-proxies, otel collector/forwarder, rclone mount) that the dynamic-sidecar creates | ||
| # directly (not as their own Swarm services), so their resource usage can still be accounted | ||
| # for when sizing the dynamic-sidecar's own Swarm service. Never a real container/image name. | ||
| HELPER_CONTAINERS_RESOURCE_KEY: Final[DockerGenericTag] = TypeAdapter(DockerGenericTag).validate_python( |
There was a problem hiding this comment.
in a sidecar pattern, I believe these are all sidecars actually.
I propose to either call these SIDECARS or SIDECAR_HELPERS to reduce confusion
|
|
||
|
|
||
| ResourcesDict: TypeAlias = dict[ResourceName, ResourceValue] | ||
| ResourcesDict: TypeAlias = dict[ResourceName, ResourceValue] # noqa: UP040 |
There was a problem hiding this comment.
can we discuss these noqa that you add here. I believe we should generally not require these anywhere. only for exceptional parts.
|
|
||
|
|
||
| ServiceResourcesDict: TypeAlias = dict[DockerGenericTag, ImageResources] | ||
| ServiceResourcesDict: TypeAlias = dict[DockerGenericTag, ImageResources] # noqa: UP040 |
| from pydantic import ByteSize, TypeAdapter | ||
| from servicelib.resources import USER_SERVICE_CPU_RESOURCE_LIMIT_ENV_KEY, USER_SERVICE_MEM_RESOURCE_LIMIT_ENV_KEY | ||
|
|
||
| _RANDOM = -1 |
There was a problem hiding this comment.
what is the _range function for? this looks re-inventing stuff that the faker already does
|
|
||
| DYNAMIC_SIDECAR_PROXY_MEMORY_RESERVATION: ByteSize = Field( | ||
| default=TypeAdapter(ByteSize).validate_python("50MiB"), | ||
| description="memory reservation for the caddy proxy container", |
There was a problem hiding this comment.
minor: I guess since this is a wrapper we do not really need to know that caddy is inside
| # 1.0 CPU, exceeding the node's available capacity after overhead). | ||
| empty_resource_entry.value["Limits"]["NanoCPUs"] = max( | ||
| empty_resource_entry.value["Limits"]["NanoCPUs"], CPU_100_PERCENT | ||
| empty_resource_entry.value["Limits"]["NanoCPUs"], int(1 * GIGA) |
|
|
||
| catalog_client = CatalogClient.instance(app) | ||
|
|
||
| # fetched early (again, later re-fetched in SendUserServicesSpec) so the exact |
There was a problem hiding this comment.
why you need to compute it twice?
|
|
||
|
|
||
| def count_required_egress_proxies(simcore_service_labels: SimcoreServiceLabels) -> int: | ||
| """Returns the number of dy-sidecar-egress-proxy containers `add_egress_configuration` will create.""" |
There was a problem hiding this comment.
this docstring is not very necessary I guess?
| # Older test images may ship with CPU.limit=0 in their labels. Apply a 1-core | ||
| # floor per container so integration tests work with pre-requirement images, | ||
| # regardless of container key names. | ||
| for image_resources in service_resources.values(): |
There was a problem hiding this comment.
maybe adding a warning could be nice here.
…y-allocate-extra-container-resources
…y-allocate-extra-container-resources
|



What do these changes do?
The problem
When a user starts an interactive service in oSPARC, the platform doesn't just start that one container — it also quietly starts several "helper" containers behind the scenes to make things work correctly:
These helper containers use real CPU and memory on the compute node they run on, but until now the platform only reserved resources for the user services, not for its helpers — in other words, it under-counted how much room a service actually needs. This under-counting means the cluster can end up placing more services on a node than it actually has capacity for, which can lead to services running slower than expected or, in busier deployments, containers being killed by the system for using more resources than were accounted for.
What changed
How the rclone helper's memory limit is sized
Previously, the rclone mount container always got the same fixed memory allocation, no matter how small or large the service it supported was — this could waste memory reservation on small services and still be too little for services that shuffle very large files.
Now, rclone's memory limit scales with the service it's attached to:
Worked examples:
This keeps small services from over-reserving memory for a helper they barely use, while still giving large, data-heavy services enough headroom for their file cache — all without ever exceeding a sane absolute ceiling.
User-facing impact
No visible change to how services look or behave day-to-day. This is a behind-the-scenes reliability and capacity-planning improvement: services get the resources they truly need — main container plus every helper it spawns — reducing the risk of slowdowns or out-of-memory/CPU-throttling issues caused by underestimated resource usage in production.
Related issue/s
How to test
Dev-ops