Skip to content

Commit b0df933

Browse files
[WIP] [Carry 2535] rootless: support detach-netns mode
When RootlessKit v2.0 (rootless-containers/rootlesskit PR 379) is installed, `containerd-rootless.sh` launches it with `--detach-netns` so as to run the daemon in the host network namespace. This will enable: - Accelerated (and deflaked) `nerdctl pull`, `nerdctl push`, `nerdctl build`, etc - Proper support for `nerdctl pull 127.0.0.1:.../...` - Proper support for `nerdctl run --net=host` Replaces Fahed Dorgaa's PR 2535 Co-authored-by: fahed dorgaa <fahed.dorgaa@gmail.com> Signed-off-by: Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
1 parent 17c93dc commit b0df933

File tree

10 files changed

+135
-3
lines changed

10 files changed

+135
-3
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ ARG STARGZ_SNAPSHOTTER_VERSION=v0.15.1
2929
# Extra deps: Encryption
3030
ARG IMGCRYPT_VERSION=v1.1.9
3131
# Extra deps: Rootless
32-
ARG ROOTLESSKIT_VERSION=v1.1.1
32+
ARG ROOTLESSKIT_VERSION=v2.0.0-alpha.2
3333
ARG SLIRP4NETNS_VERSION=v1.2.2
3434
# Extra deps: bypass4netns
3535
ARG BYPASS4NETNS_VERSION=v0.3.0
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
7c8c07c159aef32b5e68f5b8dc508dc422499744f61fa916c246bfae16a0d85c rootlesskit-aarch64.tar.gz
2+
683ba2c34bfa4a3477f9c50508a233c94b71a6eeaa0ee080abd1ebc2c09a8b9c rootlesskit-armv7l.tar.gz
3+
844b6297021d914be9f266e341ff77da4b98f43074504fe9cc020ae5c61a791d rootlesskit-ppc64le.tar.gz
4+
d317e9c519d862508d6659083f9e1773573e899aa6e48e89d121211e5e823b6a rootlesskit-riscv64.tar.gz
5+
720b425c608f8ab4326354582bc92825031d9d8c40865df155c2c7cb8368f115 rootlesskit-s390x.tar.gz
6+
d29edd2e3d903974754edb14b251ef19bfa9317e6626436fac760d1213879e8d rootlesskit-x86_64.tar.gz

extras/rootless/containerd-rootless-setuptool.sh

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,12 @@ cmd_entrypoint_check() {
143143
cmd_entrypoint_nsenter() {
144144
# No need to call init()
145145
pid=$(cat "$XDG_RUNTIME_DIR/containerd-rootless/child_pid")
146-
exec nsenter --no-fork --wd="$(pwd)" --preserve-credentials -m -n -U -t "$pid" -- "$@"
146+
n=""
147+
# If RootlessKit is running with `--detach-netns` mode, we do NOT enter the detached netns here
148+
if [ ! -e "$XDG_RUNTIME_DIR/containerd-rootless/netns" ]; then
149+
n="-n"
150+
fi
151+
exec nsenter --no-fork --wd="$(pwd)" --preserve-credentials -m $n -U -t "$pid" -- "$@"
147152
}
148153

149154
show_systemd_error() {

extras/rootless/containerd-rootless.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@ if [ -z $_CONTAINERD_ROOTLESS_CHILD ]; then
107107
export _CONTAINERD_ROOTLESS_SELINUX
108108
fi
109109
fi
110+
if rootlesskit --help | grep -qw -- "--detach-netns"; then
111+
CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS=--detach-netns $CONTAINERD_ROOTLESS_ROOTLESSKIT_FLAGS
112+
fi
110113
# Re-exec the script via RootlessKit, so as to create unprivileged {user,mount,network} namespaces.
111114
#
112115
# --copy-up allows removing/creating files in the directories by creating tmpfs and symlinks

pkg/cmd/container/create.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import (
5050
"github.com/containerd/nerdctl/pkg/namestore"
5151
"github.com/containerd/nerdctl/pkg/platformutil"
5252
"github.com/containerd/nerdctl/pkg/referenceutil"
53+
"github.com/containerd/nerdctl/pkg/rootlessutil"
5354
"github.com/containerd/nerdctl/pkg/strutil"
5455
dockercliopts "github.com/docker/cli/opts"
5556
dockeropts "github.com/docker/docker/opts"
@@ -418,6 +419,29 @@ func GenerateLogURI(dataStore string) (*url.URL, error) {
418419
}
419420

420421
func withNerdctlOCIHook(cmd string, args []string) (oci.SpecOpts, error) {
422+
if rootlessutil.IsRootless() {
423+
detachedNetNS, err := rootlessutil.DetachedNetNS()
424+
if err != nil {
425+
return nil, fmt.Errorf("failed to check whether RootlessKit is running with --detach-netns: %w", err)
426+
}
427+
if detachedNetNS != "" {
428+
// Rewrite {cmd, args} if RootlessKit with running with --detach-netns, so that the hook can gain
429+
// CAP_NET_ADMIN in the namespaces.
430+
// - Old:
431+
// - cmd: "/usr/local/bin/nerdctl"
432+
// - args: {"--data-root=/foo"}
433+
// - New:
434+
// - cmd: "/usr/bin/nsenter"
435+
// - args: {"-n/run/user/1000/containerd-rootless/netns", "--", "/usr/local/bin/nerdctl", "--data-root=/foo"}
436+
oldCmd, oldArgs := cmd, args
437+
cmd, err = exec.LookPath("nsenter")
438+
if err != nil {
439+
return nil, err
440+
}
441+
args = append([]string{"-n" + detachedNetNS, "--", oldCmd}, oldArgs...)
442+
}
443+
}
444+
421445
args = append([]string{cmd}, append(args, "internal", "oci-hook")...)
422446
return func(_ context.Context, _ oci.Client, _ *containers.Container, s *specs.Spec) error {
423447
if s.Hooks == nil {

pkg/containerutil/container_network_manager.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838
"github.com/containerd/nerdctl/pkg/mountutil"
3939
"github.com/containerd/nerdctl/pkg/netutil"
4040
"github.com/containerd/nerdctl/pkg/netutil/nettype"
41+
"github.com/containerd/nerdctl/pkg/rootlessutil"
4142
"github.com/containerd/nerdctl/pkg/strutil"
4243
"github.com/opencontainers/runtime-spec/specs-go"
4344
)
@@ -461,9 +462,37 @@ func (m *hostNetworkManager) ContainerNetworkingOpts(_ context.Context, containe
461462
}
462463
}
463464

465+
if rootlessutil.IsRootless() {
466+
detachedNetNS, err := rootlessutil.DetachedNetNS()
467+
if err != nil {
468+
return nil, nil, fmt.Errorf("failed to check whether RootlessKit is running with --detach-netns: %w", err)
469+
}
470+
if detachedNetNS != "" {
471+
// For rootless + host netns, we can't mount /sys.
472+
//
473+
// TODO: consider to just bind-mount /sys from the host with rro,
474+
// when rro is available (kernel >= 5.12, runc >= 1.1)
475+
//
476+
// Relevant: https://github.com/moby/buildkit/blob/v0.12.4/util/rootless/specconv/specconv_linux.go#L15-L34
477+
specs = append(specs, withRemoveSysfs)
478+
}
479+
}
480+
464481
return specs, cOpts, nil
465482
}
466483

484+
func withRemoveSysfs(_ context.Context, _ oci.Client, c *containers.Container, s *oci.Spec) error {
485+
var mounts []specs.Mount // nolint: prealloc
486+
for _, mount := range s.Mounts {
487+
if strings.HasPrefix(mount.Destination, "/sys") {
488+
continue
489+
}
490+
mounts = append(mounts, mount)
491+
}
492+
s.Mounts = mounts
493+
return nil
494+
}
495+
467496
// types.NetworkOptionsManager implementation for CNI networking settings.
468497
// This is a more specialized and OS-dependendant networking model so this
469498
// struct provides different implementations on different platforms.

pkg/containerutil/container_network_manager_linux.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ package containerutil
1919
import (
2020
"context"
2121
"errors"
22+
"fmt"
2223
"io/fs"
2324
"path/filepath"
2425

2526
"github.com/containerd/containerd"
27+
"github.com/containerd/containerd/containers"
2628
"github.com/containerd/containerd/oci"
2729
"github.com/containerd/log"
2830
"github.com/containerd/nerdctl/pkg/api/types"
@@ -32,6 +34,7 @@ import (
3234
"github.com/containerd/nerdctl/pkg/netutil"
3335
"github.com/containerd/nerdctl/pkg/resolvconf"
3436
"github.com/containerd/nerdctl/pkg/rootlessutil"
37+
"github.com/opencontainers/runtime-spec/specs-go"
3538
)
3639

3740
type cniNetworkManagerPlatform struct {
@@ -172,3 +175,23 @@ func (m *cniNetworkManager) buildResolvConf(resolvConfPath string) error {
172175
_, err = resolvconf.Build(resolvConfPath, append(slirp4Dns, nameServers...), searchDomains, dnsOptions)
173176
return err
174177
}
178+
179+
// withRootlessKitDetachedNetNS is used when all the following conditions are satisfied:
180+
// - Rootless mode
181+
// - RootlessKit is running with detach-netns mode (available since RootlessKit v2.0)
182+
// - the network manager is not configured to none/host/container mode
183+
func withRootlessKitDetachedNetNS(detachedNetNS, containerStateDir string) func(context.Context, oci.Client, *containers.Container, *oci.Spec) error {
184+
return func(_ context.Context, _ oci.Client, c *containers.Container, s *oci.Spec) error {
185+
for i := range s.Linux.Namespaces {
186+
n := &s.Linux.Namespaces[i]
187+
if n.Type == specs.NetworkNamespace && n.Path == "" {
188+
newChildNetNS := filepath.Join(containerStateDir, "netns")
189+
if err := netutil.NewNestedNetNS(detachedNetNS, newChildNetNS); err != nil {
190+
return fmt.Errorf("failed to nest a new netns %q inside %q: %w", newChildNetNS, detachedNetNS, err)
191+
}
192+
n.Path = newChildNetNS
193+
}
194+
}
195+
return nil
196+
}
197+
}

pkg/rootlessutil/parent_linux.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ func ParentMain(hostGatewayIP string) error {
7777
return err
7878
}
7979

80+
detachedNetNSPath, err := detachedNetNS(stateDir)
81+
if err != nil {
82+
return err
83+
}
84+
detachNetNSMode := detachedNetNSPath != ""
85+
log.L.Debugf("RootlessKit detach-netns mode: %v", detachNetNSMode)
86+
if err != nil {
87+
return err
88+
}
89+
8090
wd, err := os.Getwd()
8191
if err != nil {
8292
return err
@@ -92,10 +102,13 @@ func ParentMain(hostGatewayIP string) error {
92102
"-r/", // root dir (busybox nsenter wants this to be explicitly specified),
93103
"-w" + wd, // work dir
94104
"--preserve-credentials",
95-
"-m", "-n", "-U",
105+
"-m", "-U",
96106
"-t", strconv.Itoa(childPid),
97107
"-F", // no fork
98108
}
109+
if !detachNetNSMode {
110+
args = append(args, "-n")
111+
}
99112
args = append(args, os.Args...)
100113
log.L.Debugf("rootless parent main: executing %q with %v", arg0, args)
101114

pkg/rootlessutil/rootlessutil_linux.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package rootlessutil
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"os"
2223
"path/filepath"
@@ -80,3 +81,27 @@ func RootlessContainredSockAddress() (string, error) {
8081
}
8182
return filepath.Join(fmt.Sprintf("/proc/%d/root/run/containerd/containerd.sock", childPid)), nil
8283
}
84+
85+
// DetachedNetNS returns non-empty netns path if RootlessKit is running with --detach-netns mode.
86+
// Otherwise returns "" without an error.
87+
func DetachedNetNS() (string, error) {
88+
if !IsRootless() {
89+
return "", nil
90+
}
91+
stateDir, err := RootlessKitStateDir()
92+
if err != nil {
93+
return "", err
94+
}
95+
return detachedNetNS(stateDir)
96+
}
97+
98+
func detachedNetNS(stateDir string) (string, error) {
99+
p := filepath.Join(stateDir, "netns")
100+
if _, err := os.Stat(p); err != nil {
101+
if errors.Is(err, os.ErrNotExist) {
102+
return "", nil
103+
}
104+
return "", err
105+
}
106+
return p, nil
107+
}

pkg/rootlessutil/rootlessutil_other.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,7 @@ func ParentMain(hostGatewayIP string) error {
6666
func RootlessContainredSockAddress() (string, error) {
6767
return "", fmt.Errorf("cannot inspect RootlessKit state on non-Linux hosts")
6868
}
69+
70+
func DetachedNetNS() (string, error) {
71+
return "", nil
72+
}

0 commit comments

Comments
 (0)