mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
06f1e07655
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
9744d72c 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 deduplicate the 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]: https://github.com/opencontainers/runc/pull/1983
[2]: https://github.com/docker-archive/libcontainer/pull/418
[3]: https://github.com/docker-archive/libcontainer/pull/432
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
41 lines
792 B
Go
41 lines
792 B
Go
package libcontainer
|
|
|
|
import (
|
|
"slices"
|
|
"testing"
|
|
)
|
|
|
|
func TestPrepareEnvDedup(t *testing.T) {
|
|
tests := []struct {
|
|
env, wantEnv []string
|
|
}{
|
|
{
|
|
env: []string{},
|
|
wantEnv: []string{},
|
|
},
|
|
{
|
|
env: []string{"HOME=/root", "FOO=bar"},
|
|
wantEnv: []string{"HOME=/root", "FOO=bar"},
|
|
},
|
|
{
|
|
env: []string{"A=a", "A=b", "A=c"},
|
|
wantEnv: []string{"A=c"},
|
|
},
|
|
{
|
|
env: []string{"TERM=vt100", "HOME=/home/one", "HOME=/home/two", "TERM=xterm", "HOME=/home/three", "FOO=bar"},
|
|
wantEnv: []string{"TERM=xterm", "HOME=/home/three", "FOO=bar"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
env, _, err := prepareEnv(tc.env)
|
|
if err != nil {
|
|
t.Error(err)
|
|
continue
|
|
}
|
|
if !slices.Equal(env, tc.wantEnv) {
|
|
t.Errorf("want %v, got %v", tc.wantEnv, env)
|
|
}
|
|
}
|
|
}
|