-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
libct: speedup process.Env handling #4325
base: main
Are you sure you want to change the base?
Conversation
libcontainer/standard_init_linux.go
Outdated
@@ -277,7 +278,7 @@ func (l *linuxStandardInit) Init() error { | |||
|
|||
if l.dmzExe != nil { | |||
l.config.Args[0] = name | |||
return system.Fexecve(l.dmzExe.Fd(), l.config.Args, os.Environ()) | |||
return system.Fexecve(l.dmzExe.Fd(), l.config.Args, l.config.Env) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before we merge #4323 , maybe we should also need to include l.config.Env
when running StartContainer
hook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely something to think about. Maybe it makes sense to do it selectively (for those hooks that are run inside the container -- AFAIR not all of them are)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
9b67a06
to
b0512bc
Compare
Interesting; I didn't know about that discrepancy; sounds like something that wouldn't hurt to define in the OCI spec; given that all original implementations were in Go, and ISTR Docker also had its own code to remove duplicates, I'm inclined to describe that as the expected behavior (possibly recommend producers of the OCI config to handle duplicates themselves to prevent any ambiguity). |
I was not entirely correct here. Let me rephrase this:
On an unrelated note, I also took a look at crun and it seems it is following runc logic, calling |
PTAL @opencontainers/runc-maintainers |
LGTM, it’s a big step. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kolyshkin left some comments, but seems great it makes a difference! This is marked for 1.3, though, but I guess you asking for reviews now means you want this for 1.2?
I'm fine with this in 1.2 or 1.3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
OTOH I think this is ready for more reviews and potential inclusion. |
The PR to release 1.2 is out already, and it will be released today. I'd prefer to merge this after the 1.2.0 release, if the speedup is nice, we can add it in a patch release (or maybe to a 1.3 release in 3 months ;)) |
This is totally 1.3 material; let me rebase |
FWIW, I sent a patch to improve the performance of |
I've updated the patch (with somewhat better commit message and |
But it's a break change for |
I think we need to start adding things to the changelog in the implementation PR in general because we have missed breaking changes before (the most recent example I can think of is #3468, but I've definitely made the same mistake before as well -- it's very easy to skim over a patch when reading through the log to write the release notes). |
03faf10
to
545bd05
Compare
@cyphar the above incompatibility issue reported by @lifubang is now fixed, but in the process I had to add a small change to libcontainer API, which is documented in the CHANGELOG now. Doesn't mean this PR won't break something else, but at least I am not aware of anything. |
|
||
// In case we have any StartContainer hooks to run, and they don't | ||
// have environment configured explicitly, make sure they will be run | ||
// with the same environment as container's init. | ||
if h := l.config.Config.Hooks[configs.StartContainer]; len(h) > 0 { | ||
h.SetDefaultEnv(l.config.Env) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lifubang FYI this is the fix to the startContainer hook issue wrt environment you've reported back in June.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why added in here, but not together with the line before we prepare to run StartContainer
hooks?
I mean put them before this line:
runc/libcontainer/standard_init_linux.go
Line 282 in 5a88394
if err := l.config.Config.Hooks.Run(configs.StartContainer, s); err != nil { |
|
||
// In case we have any StartContainer hooks to run, and they don't | ||
// have environment configured explicitly, make sure they will be run | ||
// with the same environment as container's init. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a note that this behavior is not part of runtime-spec, but a de-facto historical thing we afraid to change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it worth to do it, because before this pr, it was doing in go-runtime, but now we have explicitly added the code to do this. We should let the reader know why we need this, and it may be removed in the future.
Here's what it shows on my laptop (with -count 10 -benchtime 10s, summarized by benchstat): │ sec/op │ ExecTrue-20 8.477m ± 2% ExecInBigEnv-20 61.53m ± 1% Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This is a slight refactor of TestExecInEnvironment, making it more strict wrt checking the exec output. 1. Explain why DEBUG is added twice to the env. 2. Reuse the execEnv for the check. 3. Make the check more strict -- instead of looking for substrings, check line by line. 4. Add a check for extra environment variables. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This is to ensure that changes in Process.Env handling won't affect StartContainer hook. Reported-by: lfbzhm <lifubang@acmcoder.com> Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
1. Make CommandHook.Command a pointer, which reduces the amount of data being copied when using hooks, and allows to modify command hooks. 2. Add SetDefaultEnv, which is to be used by the next commit. Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
The current implementation sets all the environment variables passed in Process.Env in the current process, one by one, then uses os.Environ to read those back. As pointed out in [1], this is slow, as runc calls os.Setenv for every variable, and there may be a few thousands of those. Looking into how os.Setenv is implemented, it is indeed slow, especially when cgo is enabled. Looking into why it was implemented the way it is, I found commit 9744d72 and traced it to [2], which discusses the actual reasons. It boils down to these two: - HOME is not passed into container as it is set in setupUser by os.Setenv and has no effect on config.Env; - there is a need to deduplication of environment variables. Yet it was decided in [2] to not go ahead with this patch, but later [3] was opened with the carry of this patch, and merged. Now, from what I see: 1. Passing environment to exec is way faster than using os.Setenv and os.Environ (tests show ~20x speed improvement in a simple Go test, and ~3x improvement in real-world test, see below). 2. Setting environment variables in the runc context may result is some ugly side effects (think GODEBUG, LD_PRELOAD, or _LIBCONTAINER_*). 3. Nothing in runtime spec says that the environment needs to be deduplicated, or the order of preference (whether the first or the last value of a variable with the same name is to be used). We should stick to what we have in order to maintain backward compatibility. So, this patch: - switches to passing env directly to exec; - adds deduplication mechanism to retain backward compatibility; - takes care to set PATH from process.Env in the current process (so that supplied PATH is used to find the binary to execute), also to retain backward compatibility; - adds HOME to process.Env if not set; - ensures any StartContainer CommandHook entries with no environment set explicitly are run with the same environment as before. Thanks to @lifubang who noticed that peculiarity. The benchmark added by the previous commit shows ~3x improvement: │ before │ after │ │ sec/op │ sec/op vs base │ ExecInBigEnv-20 61.53m ± 1% 21.87m ± 16% -64.46% (p=0.000 n=10) [1]: opencontainers#1983 [2]: docker-archive/libcontainer#418 [3]: docker-archive/libcontainer#432 Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
This is a rework/carry of #1983.
Remaining questions (and my answers to those):
Q: Are there any potential regressions (for example, from not setting values from
process.Env
to the current process?A: Pprobably not; if yes, someone is exploiting some undocumented behavior.
Q: Should deduplication show warnings (maybe promoted to errors later)?
A: For best backward compatibility, let's not do that. Can always be added later (maybe with some addition to runtime-spec).
Q: Whether a default for
PATH
(e.g"/bin:/usr/bin"
should be added, whenPATH
is not set.A: This needs to be done in runtime-spec first (document the default for PATH, then add it to runtimes).