Fixes set memory to unlimited#1127
Conversation
6254b18 to
9a25275
Compare
9a25275 to
e5dd699
Compare
libcontainer/cgroups/fs/memory.go
Outdated
| // Memeory set to '-1' which is unlimited. In this situation we need to update swap to -1 | ||
| // to avoid kernel validation failures. | ||
| if cgroup.Resources.Memory == -1 { | ||
| if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(-1, 10)); err != nil { |
There was a problem hiding this comment.
Should we make sure memory.memsw.limit_in_bytes exists first or ignore the ErrNotExist error? I think if the system does not enable memory swap, this file does not exist
There was a problem hiding this comment.
Good point, I just followed the convention (see the exiting code), I can add checks if you think it's required. However the getMemoryData is suspicious to fail too.
There was a problem hiding this comment.
This is also not libcontainer's concern, a value is assigned, libcontainer set it, simple as that. Otherwise we'll end up with another huge validation done by Docker and other high level tools.
libcontainer/cgroups/fs/memory.go
Outdated
| // Memeory set to '-1' which is unlimited. In this situation we need to update swap to -1 | ||
| // to avoid kernel validation failures. | ||
| if cgroup.Resources.Memory == -1 { | ||
| if err := writeFile(path, "memory.memsw.limit_in_bytes", strconv.FormatInt(-1, 10)); err != nil { |
There was a problem hiding this comment.
libcontainer as a low level library usually don't do this kind of thing, it only does what it's told to do.
So if docker want to set memory as -1, it should pass memorySwap as -1 as well, libcontainer only adapt the writing sequence to make the logic right.
There was a problem hiding this comment.
I agree that aggressive validation may add unnecessary overhead the the low level libraries like RunC.
There was a problem hiding this comment.
It's not "unnecessary overhead" that @hqhq is referring to (the overhead of doing a single write / check is not important here). He's talking about libcontainer doing things it wasn't told to do explicitly. libcontainer is meant to be stupid (though clever enough to know when to be clever about it), specifically this means that we in general don't take initiative past what the user has told us to do -- it's not libcontainer's job to change the settings that a user asked for.
If you wanted to idiot-proof this then you could put it inside libcontainer/specconv so that runC makes this easier for users. But then again, I'm not even sure if we should be doing that to be honest.
|
@hqhq @cyphar @coolljt0725 Shall I update the PR or these changes are sufficient? |
|
@boynux You should update your PR. I think a change like this would be sufficient, |
|
@hqhq That's actually the first thing I've tried, RunC assumption is that The other thing is should I actually check for |
|
@boynux There's no such assumption in RunC AFAIK and there shouldn't be. If only test case assumes that, you can change it. That might break Docker and that's something you should fix in moby/moby#22578. Check for |
1e7b56a to
f4d035a
Compare
|
@hqhq Now it should work as expected!
For Docker there should not be any issue, but I'm not sure about other clients that may rely on previous behavior! |
|
@hqhq is this okay for you? |
| const ( | ||
| memoryBefore = 314572800 // 300M | ||
| memoryswapBefore = 629145600 // 600M | ||
| memoryAfter = 0 // Unlimited |
There was a problem hiding this comment.
Why 0 presents unlimited? Doesn't sound reasonable. Note that unit test didn't use real cgroup fs for test, so you better add an integration test to cover this case.
There was a problem hiding this comment.
@hqhq Ok, I'll try to add integration test to cover this use case.
There was a problem hiding this comment.
@hqhq Ok I managed to write the test there is only one issue and that is how to get host memory. I checked bats and could not find a clean way (except global vars) to get the hosts total memory. If global values are find I can push the changes, otherwise please give me a direction.
There was a problem hiding this comment.
You can check cgroup values of your container like we did in: https://github.com/docker/runc/blob/master/tests/integration/cgroups.bats#L55
There was a problem hiding this comment.
Thanks for your reply.
Correct, my problem was the host machine total amount of RAM :)
I found a clean solution. Will push my changes when I did some clean ups.
There was a problem hiding this comment.
Seems memory swap is not enabled in build server kernel. I'm going to put a check there.
69b4300 to
bca1765
Compare
|
@hqhq Any updates here? |
|
Super busy these days, will take a look next week, sorry for the late. |
update.go
Outdated
| dest *uint64 | ||
| }{ | ||
| {"memory", r.Memory.Limit}, | ||
| {"memory-swap", r.Memory.Swap}, |
There was a problem hiding this comment.
The rule that '-1' means reset applies to all *.limit_in_bytes, so you can do this for all memory related limitations.
There was a problem hiding this comment.
By that do you mean kernel-memory and kernel-memory-tcp and memory-reservation also?
| sed -i "s/\(\"resources\": {\)/\1\n${DATA}/" ${BUSYBOX_BUNDLE}/config.json | ||
|
|
||
| # run a detached busybox to work with | ||
| runc run -d --console /dev/pts/ptmx test_cgroups_unlimited_mem |
There was a problem hiding this comment.
Needs update to use --console-socket.
| @@ -0,0 +1,73 @@ | |||
| #!/usr/bin/env bats | |||
There was a problem hiding this comment.
You can add this case in update.bats, no need to create a new file.
| memoryAfter = 524288000 // 500M | ||
| memoryswapBefore = 629145600 // 600M | ||
| memoryswapAfter = 629145600 // 600M | ||
| memoryswapAfter = 0 // Unlimited |
There was a problem hiding this comment.
We took negative value means not change in unit test, and that's the point of this unit test, since that's not true after your PR, you can remove that unit test, and no need to add another.
There was a problem hiding this comment.
@boynux Can you check my comment here and amend your unit test changes?
tests/integration/update.bats
Outdated
| [ "$status" -eq 0 ] | ||
|
|
||
| # Get System memory limit | ||
| SYSTEM_MEMORY=$(cat "${CGROUP_SYSTEM_MEMORY}/memory.limit_in_bytes") |
There was a problem hiding this comment.
You can do the test at line 125 so you don't have to reset to initial test value again.
There was a problem hiding this comment.
@hqhq I updated the tests please have a look if everything if fine I'll merge the commits and do a master rebase. Thank you
cbec8e1 to
d2999da
Compare
libcontainer/cgroups/fs/memory.go
Outdated
| // -1 means unlimited memory | ||
| if cgroup.Resources.Memory == -1 { | ||
| // Only set swap if it's enbled in kernel | ||
| if _, err := readFile(path, "memory.memsw.limit_in_bytes"); err == nil { |
There was a problem hiding this comment.
You can use os.Stat, otherwise looks good to me.
|
Please squash your commits. |
6b6e69c to
b218edc
Compare
|
Done! |
|
@hqhq Should I rebase my changes with master? Note: I just did it on my machine and managed to pass all the tests. If you want I can push it now. |
|
@boynux You don't have to as long as there's no conflict with master. LGTM Thanks for your patience. |
|
How should we get the second approval? |
libcontainer/cgroups/fs/memory.go
Outdated
| const cgroupKernelMemoryLimit = "memory.kmem.limit_in_bytes" | ||
| const ( | ||
| cgroupKernelMemoryLimit = "memory.kmem.limit_in_bytes" | ||
| cgroupMemorySwap = "memory.memsw.limit_in_bytes" |
There was a problem hiding this comment.
this should also be cgroupMemorySwapLimit just for consistency ?
libcontainer/cgroups/fs/memory.go
Outdated
| @@ -108,28 +121,28 @@ func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error { | |||
| // for memory and swap memory, so it won't fail because the new | |||
| // value and the old value don't fit kernel's validation. | |||
| if memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) { | |||
There was a problem hiding this comment.
when reset memory swap to -1 while the container still have memory.limit and memory.memsw.limit, this will fail and we will set memory limit to -1 while memsw is still in place. I think that will cause an error.
We should check if we want to reset memory / memoryswap here and change the order accordingly.
There was a problem hiding this comment.
If we set memsw to -1 without changing memlimit I don't think it fails, since memory will be '0' (means no change) and it just sets memsw to -1 (which means unlimited)
Did I miss something here?
There was a problem hiding this comment.
runc update test_update --memory-swap -1
[ "$status" -eq 0 ]
This test should cover what you just said, doesn't it? I just added it the test suite and it passed!
There was a problem hiding this comment.
@dqminh Changed const name to be more "consistent" and also added that test just in case ;)
There was a problem hiding this comment.
ah i see what's happening here. currentmemLimit < uint64(memSwap) with memSwap = -1 is practically always true because uint64(-1) is basically MaxUint64.
That being said, do you think its clearer to just add if cgroup.Resources.MemorySwap == -1 || memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) so we dont depend on this uint64(-1) casting. I dont really like it.
9a30595 to
61c5f4e
Compare
|
@dqminh ping! |
libcontainer/cgroups/fs/memory.go
Outdated
| @@ -108,28 +121,28 @@ func setMemoryAndSwap(path string, cgroup *configs.Cgroup) error { | |||
| // for memory and swap memory, so it won't fail because the new | |||
| // value and the old value don't fit kernel's validation. | |||
| if memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) { | |||
There was a problem hiding this comment.
ah i see what's happening here. currentmemLimit < uint64(memSwap) with memSwap = -1 is practically always true because uint64(-1) is basically MaxUint64.
That being said, do you think its clearer to just add if cgroup.Resources.MemorySwap == -1 || memoryUsage.Limit < uint64(cgroup.Resources.MemorySwap) so we dont depend on this uint64(-1) casting. I dont really like it.
tests/integration/update.bats
Outdated
| check_cgroup_value $CGROUP_MEMORY "memory.memsw.limit_in_bytes" 96468992 | ||
| fi | ||
|
|
||
| if [ -f "$CGROUP_MEMORY/memory.memsw.limit_in_bytes" ]; then |
There was a problem hiding this comment.
this test can be merge into the conditional above right, make it clearer to see that we have an existing memory-swap limit too.
There was a problem hiding this comment.
For casting -1 to uint64 I think that's a defined behavior and pretty explanatory, but since you missed it on the first try it says that can be more intuitive. being said that I'll also add a check explicitly for -1.
Kernel validation fails if memory set to -1 which is unlimited but swap is not set so. Signed-off-by: Mohammad Arab <[email protected]>
61c5f4e to
18ebc51
Compare
|
@hqhq sorry for the trouble but we need your approval again. |
In cgroupv1, command `runc update $ID --memory -1` sets both memory and memory+swap to -1 (aka unlimited). This was introduced by commit 18ebc51 (Reset Swap when memory is set to unlimited, Oct 19 2016). This is not the case for cgroupv2. Fix it. References: - opencontainers#1127 - moby/moby#22578 Signed-off-by: Kir Kolyshkin <[email protected]>
In cgroupv1, command `runc update $ID --memory -1` sets both memory and memory+swap to -1 (aka unlimited). This was introduced by commit 18ebc51 (Reset Swap when memory is set to unlimited, Oct 19 2016). This is not the case for cgroupv2. Fix it. References: - opencontainers#1127 - moby/moby#22578 Signed-off-by: Kir Kolyshkin <[email protected]>
Should set Swap to '-1' (unlimited) if memory is set to unlimited. Otherwise kernel validation fails.
these changes required to fix this issue in Docker project: moby/moby#22578