Skip to content

Commit 9e6635d

Browse files
authored
[user-namespaces] Support FUSE FS shift (#3384)
[user-namespaces] Support FUSE FS shift
1 parent 110ada6 commit 9e6635d

File tree

17 files changed

+703
-125
lines changed

17 files changed

+703
-125
lines changed

chart/templates/ws-daemon-configmap.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ daemon:
3030
backup:
3131
timeout: "5m"
3232
attempts: 3
33+
userNamespaces:
34+
fsShift: {{ $comp.userNamespaces.fsShift | default "fuse" }}
3335
fullWorkspaceBackup:
3436
workdir: "/mnt/node0/gitpod-{{ .Release.Namespace }}"
3537
initializer:

chart/values.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,11 @@ components:
414414
# - /run/containerd/io.containerd.runtime.v1.linux/moby
415415
# - /run/containerd/io.containerd.runtime.v2.task/k8s.io
416416
userNamespaces:
417+
# Valid values for fsShift are:
418+
# fuse: uses fuse-overlayfs
419+
# shiftfs: uses shiftfs. Depending on the underlying OS/distribution you
420+
# might want to enable the shiftfsModuleLoader.
421+
fsShift: fuse
417422
shiftfsModuleLoader:
418423
enabled: false
419424
imageName: "shiftfs-module-loader"

components/supervisor/BUILD.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ packages:
2626
- :app
2727
- components/supervisor/frontend:app
2828
- components/workspacekit:app
29+
- components/workspacekit:fuse-overlayfs
2930
argdeps:
3031
- imageRepoBase
3132
config:

components/supervisor/leeway.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ WORKDIR "/.supervisor"
1313
COPY components-supervisor--app/supervisor /.supervisor/supervisor
1414
COPY supervisor-config.json /.supervisor/supervisor-config.json
1515
COPY components-workspacekit--app/workspacekit /.supervisor/workspacekit
16+
COPY components-workspacekit--fuse-overlayfs/fuse-overlayfs /.supervisor/fuse-overlayfs
1617

1718
ENTRYPOINT ["/.supervisor/supervisor"]

components/workspacekit/BUILD.yaml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,11 @@ packages:
1515
config:
1616
packaging: app
1717
buildCommand: ["go", "build", "-ldflags", "-w -extldflags \"-static\""]
18-
- name: libseccomp
18+
- name: fuse-overlayfs
1919
type: generic
2020
config:
2121
commands:
22-
- ["sh", "-c", "curl -L https://github.com/seccomp/libseccomp/releases/download/v2.5.1/libseccomp-2.5.1.tar.gz | tar xz"]
23-
- ["sh", "-c", "cd libseccomp-2.5.1 && ./configure --prefix=$PWD/../lib && make && make install"]
22+
- ["sh", "-c", "curl -o fuse-overlayfs -L https://github.com/containers/fuse-overlayfs/releases/download/v1.4.0/fuse-overlayfs-x86_64 && chmod +x fuse-overlayfs"]
2423
- name: lib
2524
type: go
2625
srcs:

components/workspacekit/cmd/rings.go

Lines changed: 75 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"os/signal"
1414
"path/filepath"
1515
"runtime"
16+
"strings"
1617
"syscall"
1718
"time"
1819

@@ -28,6 +29,7 @@ import (
2829
"github.com/gitpod-io/gitpod/common-go/log"
2930
"github.com/gitpod-io/gitpod/workspacekit/pkg/lift"
3031
"github.com/gitpod-io/gitpod/workspacekit/pkg/seccomp"
32+
"github.com/gitpod-io/gitpod/ws-daemon/api"
3133
daemonapi "github.com/gitpod-io/gitpod/ws-daemon/api"
3234
)
3335

@@ -70,7 +72,7 @@ var ring0Cmd = &cobra.Command{
7072
}
7173
defer conn.Close()
7274

73-
_, err = client.PrepareForUserNS(ctx, &daemonapi.PrepareForUserNSRequest{})
75+
prep, err := client.PrepareForUserNS(ctx, &daemonapi.PrepareForUserNSRequest{})
7476
if err != nil {
7577
log.WithError(err).Fatal("cannot prepare for user namespaces")
7678
return
@@ -95,7 +97,7 @@ var ring0Cmd = &cobra.Command{
9597
cmd.Stdin = os.Stdin
9698
cmd.Stdout = os.Stdout
9799
cmd.Stderr = os.Stderr
98-
cmd.Env = os.Environ()
100+
cmd.Env = append(os.Environ(), "WORKSPACEKIT_FSSHIFT="+prep.FsShift.String())
99101

100102
if err := cmd.Start(); err != nil {
101103
log.WithError(err).Error("failed to start ring0")
@@ -183,11 +185,11 @@ var ring1Cmd = &cobra.Command{
183185
}
184186
defer conn.Close()
185187

188+
mapping := []*daemonapi.WriteIDMappingRequest_Mapping{
189+
{ContainerId: 0, HostId: 33333, Size: 1},
190+
{ContainerId: 1, HostId: 100000, Size: 65534},
191+
}
186192
if !ring1Opts.MappingEstablished {
187-
mapping := []*daemonapi.WriteIDMappingRequest_Mapping{
188-
{ContainerId: 0, HostId: 33333, Size: 1},
189-
{ContainerId: 1, HostId: 100000, Size: 65534},
190-
}
191193
_, err = client.WriteIDMapping(ctx, &daemonapi.WriteIDMappingRequest{Pid: int64(os.Getpid()), Gid: false, Mapping: mapping})
192194
if err != nil {
193195
log.WithError(err).Error("cannot establish UID mapping")
@@ -223,23 +225,46 @@ var ring1Cmd = &cobra.Command{
223225
log.WithError(err).Fatal("cannot create tempdir")
224226
}
225227

226-
mnts := []struct {
228+
var fsshift api.FSShiftMethod
229+
if v, ok := api.FSShiftMethod_value[os.Getenv("WORKSPACEKIT_FSSHIFT")]; !ok {
230+
log.WithField("fsshift", os.Getenv("WORKSPACEKIT_FSSHIFT")).Fatal("unknown FS shift method")
231+
} else {
232+
fsshift = api.FSShiftMethod(v)
233+
}
234+
235+
type mnte struct {
227236
Target string
228237
Source string
229238
FSType string
230239
Flags uintptr
231-
}{
232-
// TODO(cw): pull mark mount location from config
233-
{Target: "/", Source: "/.workspace/mark", FSType: "shiftfs"},
234-
{Target: "/sys", Flags: unix.MS_BIND | unix.MS_REC},
235-
{Target: "/dev", Flags: unix.MS_BIND | unix.MS_REC},
236-
// TODO(cw): only mount /workspace if it's in the mount table, i.e. this isn't an FWB workspace
237-
{Target: "/workspace", Flags: unix.MS_BIND | unix.MS_REC},
238-
{Target: "/etc/hosts", Flags: unix.MS_BIND | unix.MS_REC},
239-
{Target: "/etc/hostname", Flags: unix.MS_BIND | unix.MS_REC},
240-
{Target: "/etc/resolv.conf", Flags: unix.MS_BIND | unix.MS_REC},
241-
{Target: "/tmp", Source: "tmpfs", FSType: "tmpfs"},
242240
}
241+
242+
var mnts []mnte
243+
244+
switch fsshift {
245+
case api.FSShiftMethod_FUSE:
246+
mnts = append(mnts,
247+
mnte{Target: "/", Source: "/.workspace/mark", Flags: unix.MS_BIND | unix.MS_REC},
248+
)
249+
case api.FSShiftMethod_SHIFTFS:
250+
mnts = append(mnts,
251+
mnte{Target: "/", Source: "/.workspace/mark", FSType: "shiftfs"},
252+
)
253+
default:
254+
log.WithField("fsshift", fsshift).Fatal("unknown FS shift method")
255+
}
256+
257+
mnts = append(mnts,
258+
mnte{Target: "/sys", Flags: unix.MS_BIND | unix.MS_REC},
259+
mnte{Target: "/dev", Flags: unix.MS_BIND | unix.MS_REC},
260+
// TODO(cw): only mount /workspace if it's in the mount table, i.e. this isn't an FWB workspace
261+
mnte{Target: "/workspace", Flags: unix.MS_BIND | unix.MS_REC},
262+
mnte{Target: "/etc/hosts", Flags: unix.MS_BIND | unix.MS_REC},
263+
mnte{Target: "/etc/hostname", Flags: unix.MS_BIND | unix.MS_REC},
264+
mnte{Target: "/etc/resolv.conf", Flags: unix.MS_BIND | unix.MS_REC},
265+
mnte{Target: "/tmp", Source: "tmpfs", FSType: "tmpfs"},
266+
)
267+
243268
for _, m := range mnts {
244269
dst := filepath.Join(ring2Root, m.Target)
245270
_ = os.MkdirAll(dst, 0644)
@@ -265,6 +290,14 @@ var ring1Cmd = &cobra.Command{
265290
}
266291
}
267292

293+
env := make([]string, 0, len(os.Environ()))
294+
for _, e := range os.Environ() {
295+
if strings.HasPrefix(e, "WORKSPACEKIT_") {
296+
continue
297+
}
298+
env = append(env, e)
299+
}
300+
268301
socketFN := filepath.Join(os.TempDir(), fmt.Sprintf("workspacekit-ring1-%d.unix", time.Now().UnixNano()))
269302
skt, err := net.Listen("unix", socketFN)
270303
if err != nil {
@@ -283,7 +316,7 @@ var ring1Cmd = &cobra.Command{
283316
cmd.Stdin = os.Stdin
284317
cmd.Stdout = os.Stdout
285318
cmd.Stderr = os.Stderr
286-
cmd.Env = os.Environ()
319+
cmd.Env = env
287320
if err := cmd.Start(); err != nil {
288321
log.WithError(err).Error("failed to start the child process")
289322
failed = true
@@ -358,8 +391,9 @@ var ring1Cmd = &cobra.Command{
358391

359392
log.Info("signaling to child process")
360393
_, err = msgutil.MarshalToWriter(ring2Conn, ringSyncMsg{
361-
Stage: 1,
362-
Rootfs: ring2Root,
394+
Stage: 1,
395+
Rootfs: ring2Root,
396+
FSShift: fsshift,
363397
})
364398
if err != nil {
365399
log.WithError(err).Error("cannot send ring sync msg to ring2")
@@ -504,7 +538,7 @@ var ring2Cmd = &cobra.Command{
504538
return
505539
}
506540

507-
err = pivotRoot(msg.Rootfs)
541+
err = pivotRoot(msg.Rootfs, msg.FSShift)
508542
if err != nil {
509543
log.WithError(err).Error("cannot pivot root")
510544
failed = true
@@ -562,13 +596,27 @@ var ring2Cmd = &cobra.Command{
562596
// filesystem, and everything else is cleaned up.
563597
//
564598
// copied from runc: https://github.com/opencontainers/runc/blob/cf6c074115d00c932ef01dedb3e13ba8b8f964c3/libcontainer/rootfs_linux.go#L760
565-
func pivotRoot(rootfs string) error {
599+
func pivotRoot(rootfs string, fsshift api.FSShiftMethod) error {
566600
// While the documentation may claim otherwise, pivot_root(".", ".") is
567601
// actually valid. What this results in is / being the new root but
568602
// /proc/self/cwd being the old root. Since we can play around with the cwd
569603
// with pivot_root this allows us to pivot without creating directories in
570604
// the rootfs. Shout-outs to the LXC developers for giving us this idea.
571605

606+
if fsshift == api.FSShiftMethod_FUSE {
607+
err := unix.Chroot(rootfs)
608+
if err != nil {
609+
return fmt.Errorf("cannot chroot: %v", err)
610+
}
611+
612+
err = unix.Chdir("/")
613+
if err != nil {
614+
return fmt.Errorf("cannot chdir to new root :%v", err)
615+
}
616+
617+
return nil
618+
}
619+
572620
oldroot, err := unix.Open("/", unix.O_DIRECTORY|unix.O_RDONLY, 0)
573621
if err != nil {
574622
return err
@@ -616,6 +664,7 @@ func pivotRoot(rootfs string) error {
616664
if err := unix.Chdir("/"); err != nil {
617665
return fmt.Errorf("chdir / %s", err)
618666
}
667+
619668
return nil
620669
}
621670

@@ -631,8 +680,9 @@ func sleepForDebugging() {
631680
}
632681

633682
type ringSyncMsg struct {
634-
Stage int `json:"stage"`
635-
Rootfs string `json:"rootfs"`
683+
Stage int `json:"stage"`
684+
Rootfs string `json:"rootfs"`
685+
FSShift api.FSShiftMethod `json:"fsshift"`
636686
}
637687

638688
// ConnectToInWorkspaceDaemonService attempts to connect to the InWorkspaceService offered by the ws-daemon.

components/workspacekit/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.16
55
replace github.com/seccomp/libseccomp-golang => github.com/kinvolk/libseccomp-golang v0.9.2-0.20201113182948-883917843313
66

77
require (
8+
github.com/containerd/containerd v1.4.1
89
github.com/gitpod-io/gitpod/common-go v0.0.0-00010101000000-000000000000
910
github.com/gitpod-io/gitpod/ws-daemon/api v0.0.0-00010101000000-000000000000
1011
github.com/moby/sys/mountinfo v0.4.0

0 commit comments

Comments
 (0)