From 1537f31381b946f395348d9891602a1e4a2c1070 Mon Sep 17 00:00:00 2001 From: Evan Hazlett Date: Tue, 1 May 2018 09:39:31 -0400 Subject: [PATCH 1/2] Add install support for binary images This adds a way for users to programatically install containerd binary dependencies. With runtime v2 and new shim's being built, it will be a challenge to get those onto machines. Users would have to find the link, download, place it in their path, yada yada yada. With this functionality of a managed `/opt` directory, containerd can use existing image and distribution infra. to get binarys, shims, etc onto the system. Configuration: *default:* `/opt/containerd` *containerd config:* ```toml [plugins.opt] path = "/opt/mypath" ``` Usage: *code:* ```go image, err := client.Pull(ctx, "docker.io/crosbymichael/runc:latest") client.Install(ctx, image) ``` *ctr:* ```bash ctr content fetch docker.io/crosbymichael/runc:latest ctr install docker.io/crosbymichael/runc:latest ``` You can manage versions and see what is running via standard image commands. Images: These images MUST be small and only contain binaries. ```Dockerfile FROM scratch Add runc /bin/runc ``` Containerd will only extract files in `/bin` of the image. Later on, we can add support for `/lib`. The code adds a service to manage an `/opt/containerd` directory and provide that path to callers via the introspection service. How to Test: Delete runc from your system. ```bash > sudo ctr run --rm docker.io/library/redis:alpine redis ctr: OCI runtime create failed: unable to retrieve OCI runtime error (open /run/containerd/io.containerd.runtime.v1.linux/default/redis/log.json: no such file or directory): exec: "runc": executable file not found in $PATH: unknown > sudo ctr content fetch docker.io/crosbymichael/runc:latest > sudo ctr install docker.io/crosbymichael/runc:latest > sudo ctr run --rm docker.io/library/redis:alpine redis 1:C 01 Aug 15:59:52.864 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 1:C 01 Aug 15:59:52.864 # Redis version=4.0.10, bits=64, commit=00000000, modified=0, pid=1, just started 1:C 01 Aug 15:59:52.864 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf 1:M 01 Aug 15:59:52.866 # You requested maxclients of 10000 requiring at least 10032 max file descriptors. 1:M 01 Aug 15:59:52.866 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted. 1:M 01 Aug 15:59:52.866 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'. 1:M 01 Aug 15:59:52.870 * Running mode=standalone, port=6379. 1:M 01 Aug 15:59:52.870 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 1:M 01 Aug 15:59:52.870 # Server initialized 1:M 01 Aug 15:59:52.870 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. 1:M 01 Aug 15:59:52.870 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled. 1:M 01 Aug 15:59:52.870 * Ready to accept connections ^C1:signal-handler (1533139193) Received SIGINT scheduling shutdown... 1:M 01 Aug 15:59:53.472 # User requested shutdown... 1:M 01 Aug 15:59:53.472 * Saving the final RDB snapshot before exiting. 1:M 01 Aug 15:59:53.484 * DB saved on disk 1:M 01 Aug 15:59:53.484 # Redis is now ready to exit, bye bye... ``` Signed-off-by: Evan Hazlett Signed-off-by: Michael Crosby --- archive/tar.go | 7 +++ archive/tar_opts.go | 18 +++++++ archive/tar_opts_unix.go | 1 + archive/tar_opts_windows.go | 1 + cmd/containerd/builtins.go | 1 + cmd/ctr/app/main.go | 2 + cmd/ctr/commands/install/install.go | 43 ++++++++++++++++ install.go | 76 +++++++++++++++++++++++++++++ services/opt/path_unix.go | 21 ++++++++ services/opt/path_windows.go | 25 ++++++++++ services/opt/service.go | 58 ++++++++++++++++++++++ 11 files changed, 253 insertions(+) create mode 100644 cmd/ctr/commands/install/install.go create mode 100644 install.go create mode 100644 services/opt/path_unix.go create mode 100644 services/opt/path_windows.go create mode 100644 services/opt/service.go diff --git a/archive/tar.go b/archive/tar.go index 6246691aa..9eeea7816 100644 --- a/archive/tar.go +++ b/archive/tar.go @@ -114,6 +114,9 @@ func Apply(ctx context.Context, root string, r io.Reader, opts ...ApplyOpt) (int return 0, errors.Wrap(err, "failed to apply option") } } + if options.Filter == nil { + options.Filter = all + } return apply(ctx, root, tar.NewReader(r), options) } @@ -155,6 +158,10 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) + if !options.Filter(hdr) { + continue + } + if skipFile(hdr) { log.G(ctx).Warnf("file %q ignored: archive may not be supported on system", hdr.Name) continue diff --git a/archive/tar_opts.go b/archive/tar_opts.go index b0f86abdf..a0ee39a03 100644 --- a/archive/tar_opts.go +++ b/archive/tar_opts.go @@ -16,5 +16,23 @@ package archive +import "archive/tar" + // ApplyOpt allows setting mutable archive apply properties on creation type ApplyOpt func(options *ApplyOptions) error + +// Filter specific files from the archive +type Filter func(*tar.Header) bool + +// all allows all files +func all(_ *tar.Header) bool { + return true +} + +// WithFilter uses the filter to select which files are to be extracted. +func WithFilter(f Filter) ApplyOpt { + return func(options *ApplyOptions) error { + options.Filter = f + return nil + } +} diff --git a/archive/tar_opts_unix.go b/archive/tar_opts_unix.go index e19afddd2..173826967 100644 --- a/archive/tar_opts_unix.go +++ b/archive/tar_opts_unix.go @@ -20,4 +20,5 @@ package archive // ApplyOptions provides additional options for an Apply operation type ApplyOptions struct { + Filter Filter // Filter tar headers } diff --git a/archive/tar_opts_windows.go b/archive/tar_opts_windows.go index 0991ab094..e4b15a163 100644 --- a/archive/tar_opts_windows.go +++ b/archive/tar_opts_windows.go @@ -22,6 +22,7 @@ package archive type ApplyOptions struct { ParentLayerPaths []string // Parent layer paths used for Windows layer apply IsWindowsContainerLayer bool // True if the tar stream to be applied is a Windows Container Layer + Filter Filter // Filter tar headers } // WithParentLayers adds parent layers to the apply process this is required diff --git a/cmd/containerd/builtins.go b/cmd/containerd/builtins.go index 17fa9f629..b120b6078 100644 --- a/cmd/containerd/builtins.go +++ b/cmd/containerd/builtins.go @@ -30,6 +30,7 @@ import ( _ "github.com/containerd/containerd/services/introspection" _ "github.com/containerd/containerd/services/leases" _ "github.com/containerd/containerd/services/namespaces" + _ "github.com/containerd/containerd/services/opt" _ "github.com/containerd/containerd/services/snapshots" _ "github.com/containerd/containerd/services/tasks" _ "github.com/containerd/containerd/services/version" diff --git a/cmd/ctr/app/main.go b/cmd/ctr/app/main.go index 37ecf8707..9c33216b3 100644 --- a/cmd/ctr/app/main.go +++ b/cmd/ctr/app/main.go @@ -24,6 +24,7 @@ import ( "github.com/containerd/containerd/cmd/ctr/commands/content" "github.com/containerd/containerd/cmd/ctr/commands/events" "github.com/containerd/containerd/cmd/ctr/commands/images" + "github.com/containerd/containerd/cmd/ctr/commands/install" "github.com/containerd/containerd/cmd/ctr/commands/leases" namespacesCmd "github.com/containerd/containerd/cmd/ctr/commands/namespaces" "github.com/containerd/containerd/cmd/ctr/commands/plugins" @@ -103,6 +104,7 @@ containerd CLI run.Command, snapshots.Command, tasks.Command, + install.Command, }, extraCmds...) app.Before = func(context *cli.Context) error { if context.GlobalBool("debug") { diff --git a/cmd/ctr/commands/install/install.go b/cmd/ctr/commands/install/install.go new file mode 100644 index 000000000..ad0cb5862 --- /dev/null +++ b/cmd/ctr/commands/install/install.go @@ -0,0 +1,43 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package install + +import ( + "github.com/containerd/containerd/cmd/ctr/commands" + "github.com/urfave/cli" +) + +// Command to install binary packages +var Command = cli.Command{ + Name: "install", + Usage: "install a new package", + ArgsUsage: "", + Description: "install a new package", + Action: func(context *cli.Context) error { + client, ctx, cancel, err := commands.NewClient(context) + if err != nil { + return err + } + defer cancel() + ref := context.Args().First() + image, err := client.GetImage(ctx, ref) + if err != nil { + return err + } + return client.Install(ctx, image) + }, +} diff --git a/install.go b/install.go new file mode 100644 index 000000000..7fa967aca --- /dev/null +++ b/install.go @@ -0,0 +1,76 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containerd + +import ( + "archive/tar" + "context" + "errors" + "path/filepath" + + introspectionapi "github.com/containerd/containerd/api/services/introspection/v1" + "github.com/containerd/containerd/archive" + "github.com/containerd/containerd/archive/compression" + "github.com/containerd/containerd/content" + "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" +) + +// Install a binary image into the opt service +func (c *Client) Install(ctx context.Context, image Image) error { + resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{ + Filters: []string{ + "id==opt", + }, + }) + if err != nil { + return err + } + if len(resp.Plugins) != 1 { + return errors.New("opt service not enabled") + } + path := resp.Plugins[0].Exports["path"] + if path == "" { + return errors.New("opt path not exported") + } + var ( + cs = image.ContentStore() + platform = platforms.Default() + ) + manifest, err := images.Manifest(ctx, cs, image.Target(), platform) + if err != nil { + return err + } + for _, layer := range manifest.Layers { + ra, err := cs.ReaderAt(ctx, layer) + if err != nil { + return err + } + cr := content.NewReader(ra) + r, err := compression.DecompressStream(cr) + if err != nil { + return err + } + defer r.Close() + if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) bool { + return filepath.Dir(hdr.Name) == "bin" + })); err != nil { + return err + } + } + return nil +} diff --git a/services/opt/path_unix.go b/services/opt/path_unix.go new file mode 100644 index 000000000..b4d996cad --- /dev/null +++ b/services/opt/path_unix.go @@ -0,0 +1,21 @@ +// +build !windows + +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package opt + +const defaultPath = "/opt/containerd" diff --git a/services/opt/path_windows.go b/services/opt/path_windows.go new file mode 100644 index 000000000..d379920f2 --- /dev/null +++ b/services/opt/path_windows.go @@ -0,0 +1,25 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package opt + +import ( + "path/filepath" + + "github.com/containerd/containerd/defaults" +) + +var defaultPath = filepath.Join(defaults.DefaultRootDir, "opt") diff --git a/services/opt/service.go b/services/opt/service.go new file mode 100644 index 000000000..ce07cc94e --- /dev/null +++ b/services/opt/service.go @@ -0,0 +1,58 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package opt + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/containerd/containerd/plugin" + "github.com/pkg/errors" +) + +// Config for the opt manager +type Config struct { + // Path for the opt directory + Path string `toml:"path"` +} + +func init() { + plugin.Register(&plugin.Registration{ + Type: plugin.InternalPlugin, + ID: "opt", + Config: &Config{ + Path: defaultPath, + }, + InitFn: func(ic *plugin.InitContext) (interface{}, error) { + path := ic.Config.(*Config).Path + ic.Meta.Exports["path"] = path + + bin := filepath.Join(path, "bin") + if err := os.MkdirAll(bin, 0711); err != nil { + return nil, err + } + if err := os.Setenv("PATH", fmt.Sprintf("%s:%s", bin, os.Getenv("PATH"))); err != nil { + return nil, errors.Wrapf(err, "set binary image directory in path %s", bin) + } + return &manager{}, nil + }, + }) +} + +type manager struct { +} From 5a47c5ec1d25a1192ed9e14f6807d030fb01b6b8 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 1 Aug 2018 15:49:39 -0400 Subject: [PATCH 2/2] Add lib support as an option Some images like `criu` will have extra libs that it requires. This adds lib support via LD_LIBRARY_PATH and InstallOpts Signed-off-by: Michael Crosby --- archive/tar.go | 6 ++- archive/tar_opts.go | 6 +-- cmd/ctr/commands/install/install.go | 20 +++++++++- install.go | 23 +++++++++-- install_opts.go | 38 +++++++++++++++++++ services/opt/service.go | 10 +++++ vendor.conf | 2 +- .../containerd/go-runc/command_linux.go | 2 + .../containerd/go-runc/command_other.go | 5 ++- 9 files changed, 101 insertions(+), 11 deletions(-) create mode 100644 install_opts.go diff --git a/archive/tar.go b/archive/tar.go index 9eeea7816..ac04e3e02 100644 --- a/archive/tar.go +++ b/archive/tar.go @@ -158,7 +158,11 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO // Normalize name, for safety and for a simple is-root check hdr.Name = filepath.Clean(hdr.Name) - if !options.Filter(hdr) { + accept, err := options.Filter(hdr) + if err != nil { + return 0, err + } + if !accept { continue } diff --git a/archive/tar_opts.go b/archive/tar_opts.go index a0ee39a03..a08bc102a 100644 --- a/archive/tar_opts.go +++ b/archive/tar_opts.go @@ -22,11 +22,11 @@ import "archive/tar" type ApplyOpt func(options *ApplyOptions) error // Filter specific files from the archive -type Filter func(*tar.Header) bool +type Filter func(*tar.Header) (bool, error) // all allows all files -func all(_ *tar.Header) bool { - return true +func all(_ *tar.Header) (bool, error) { + return true, nil } // WithFilter uses the filter to select which files are to be extracted. diff --git a/cmd/ctr/commands/install/install.go b/cmd/ctr/commands/install/install.go index ad0cb5862..4113418fb 100644 --- a/cmd/ctr/commands/install/install.go +++ b/cmd/ctr/commands/install/install.go @@ -17,6 +17,7 @@ package install import ( + "github.com/containerd/containerd" "github.com/containerd/containerd/cmd/ctr/commands" "github.com/urfave/cli" ) @@ -27,6 +28,16 @@ var Command = cli.Command{ Usage: "install a new package", ArgsUsage: "", Description: "install a new package", + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "libs,l", + Usage: "install libs from the image", + }, + cli.BoolFlag{ + Name: "replace,r", + Usage: "replace any binaries or libs in the opt directory", + }, + }, Action: func(context *cli.Context) error { client, ctx, cancel, err := commands.NewClient(context) if err != nil { @@ -38,6 +49,13 @@ var Command = cli.Command{ if err != nil { return err } - return client.Install(ctx, image) + var opts []containerd.InstallOpts + if context.Bool("libs") { + opts = append(opts, containerd.WithInstallLibs) + } + if context.Bool("replace") { + opts = append(opts, containerd.WithInstallReplace) + } + return client.Install(ctx, image, opts...) }, } diff --git a/install.go b/install.go index 7fa967aca..2aa8b0394 100644 --- a/install.go +++ b/install.go @@ -19,7 +19,7 @@ package containerd import ( "archive/tar" "context" - "errors" + "os" "path/filepath" introspectionapi "github.com/containerd/containerd/api/services/introspection/v1" @@ -28,10 +28,11 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" "github.com/containerd/containerd/platforms" + "github.com/pkg/errors" ) // Install a binary image into the opt service -func (c *Client) Install(ctx context.Context, image Image) error { +func (c *Client) Install(ctx context.Context, image Image, opts ...InstallOpts) error { resp, err := c.IntrospectionService().Plugins(ctx, &introspectionapi.PluginsRequest{ Filters: []string{ "id==opt", @@ -47,6 +48,10 @@ func (c *Client) Install(ctx context.Context, image Image) error { if path == "" { return errors.New("opt path not exported") } + var config InstallConfig + for _, o := range opts { + o(&config) + } var ( cs = image.ContentStore() platform = platforms.Default() @@ -66,8 +71,18 @@ func (c *Client) Install(ctx context.Context, image Image) error { return err } defer r.Close() - if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) bool { - return filepath.Dir(hdr.Name) == "bin" + if _, err := archive.Apply(ctx, path, r, archive.WithFilter(func(hdr *tar.Header) (bool, error) { + d := filepath.Dir(hdr.Name) + result := d == "bin" + if config.Libs { + result = result || d == "lib" + } + if result && !config.Replace { + if _, err := os.Lstat(filepath.Join(path, hdr.Name)); err == nil { + return false, errors.Errorf("cannot replace %s in %s", hdr.Name, path) + } + } + return result, nil })); err != nil { return err } diff --git a/install_opts.go b/install_opts.go new file mode 100644 index 000000000..b11e7f3d6 --- /dev/null +++ b/install_opts.go @@ -0,0 +1,38 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package containerd + +// InstallOpts configures binary installs +type InstallOpts func(*InstallConfig) + +// InstallConfig sets the binary install configuration +type InstallConfig struct { + // Libs installs libs from the image + Libs bool + // Replace will overwrite existing binaries or libs in the opt directory + Replace bool +} + +// WithInstallLibs installs libs from the image +func WithInstallLibs(c *InstallConfig) { + c.Libs = true +} + +// WithInstallReplace will replace existing files +func WithInstallReplace(c *InstallConfig) { + c.Replace = true +} diff --git a/services/opt/service.go b/services/opt/service.go index ce07cc94e..756a05f0a 100644 --- a/services/opt/service.go +++ b/services/opt/service.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "github.com/containerd/containerd/plugin" "github.com/pkg/errors" @@ -49,6 +50,15 @@ func init() { if err := os.Setenv("PATH", fmt.Sprintf("%s:%s", bin, os.Getenv("PATH"))); err != nil { return nil, errors.Wrapf(err, "set binary image directory in path %s", bin) } + if runtime.GOOS != "windows" { + lib := filepath.Join(path, "lib") + if err := os.MkdirAll(lib, 0711); err != nil { + return nil, err + } + if err := os.Setenv("LD_LIBRARY_PATH", fmt.Sprintf("%s:%s", os.Getenv("LD_LIBRARY_PATH"), lib)); err != nil { + return nil, errors.Wrapf(err, "set binary lib directory in path %s", lib) + } + } return &manager{}, nil }, }) diff --git a/vendor.conf b/vendor.conf index 65b5f6973..99f3b7b16 100644 --- a/vendor.conf +++ b/vendor.conf @@ -1,4 +1,4 @@ -github.com/containerd/go-runc 14606eb66abd9e834e3bd22a4f5f46a3aad54c54 +github.com/containerd/go-runc edcf3de1f4971445c42d61f20d506b30612aa031 github.com/containerd/console 4d8a41f4ce5b9bae77c41786ea2458330f43f081 github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 diff --git a/vendor/github.com/containerd/go-runc/command_linux.go b/vendor/github.com/containerd/go-runc/command_linux.go index d97400d06..71b52f9de 100644 --- a/vendor/github.com/containerd/go-runc/command_linux.go +++ b/vendor/github.com/containerd/go-runc/command_linux.go @@ -18,6 +18,7 @@ package runc import ( "context" + "os" "os/exec" "syscall" ) @@ -31,6 +32,7 @@ func (r *Runc) command(context context.Context, args ...string) *exec.Cmd { cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: r.Setpgid, } + cmd.Env = os.Environ() if r.PdeathSignal != 0 { cmd.SysProcAttr.Pdeathsig = r.PdeathSignal } diff --git a/vendor/github.com/containerd/go-runc/command_other.go b/vendor/github.com/containerd/go-runc/command_other.go index 5209a14fb..b8fd4b866 100644 --- a/vendor/github.com/containerd/go-runc/command_other.go +++ b/vendor/github.com/containerd/go-runc/command_other.go @@ -20,6 +20,7 @@ package runc import ( "context" + "os" "os/exec" ) @@ -28,5 +29,7 @@ func (r *Runc) command(context context.Context, args ...string) *exec.Cmd { if command == "" { command = DefaultCommand } - return exec.CommandContext(context, command, append(r.args(), args...)...) + cmd := exec.CommandContext(context, command, append(r.args(), args...)...) + cmd.Env = os.Environ() + return cmd }