Skip to content
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

[docker] Introduce socket activated docker-up #4018

Merged
merged 5 commits into from
Apr 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions components/docker-up/docker-up/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
sigproxysignal "github.com/rootless-containers/rootlesskit/pkg/sigproxy/signal"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
"golang.org/x/sys/unix"
)

var log *logrus.Entry
Expand All @@ -45,10 +46,14 @@ var opts struct {
//go:embed slirp4netns
var binaries embed.FS

const (
dockerSocketFN = "/var/run/docker.sock"
)

func main() {
self, err := os.Executable()
if err != nil {
log.WithError(err).Fatal()
logrus.WithError(err).Fatal()
}

pflag.BoolVarP(&opts.Verbose, "verbose", "v", false, "enables verbose logging")
Expand All @@ -68,6 +73,11 @@ func main() {
cmd = args[0]
}

listenFD := os.Getenv("LISTEN_FDS") != ""
if _, err := os.Stat(dockerSocketFN); !listenFD && (err == nil || !os.IsNotExist(err)) {
logger.Fatalf("Docker socket already exists at %s.\nIn a Gitpod workspace Docker will start automatically when used.\nIf all else fails, please remove %s and try again.", dockerSocketFN, dockerSocketFN)
}

switch cmd {
case "child":
log = logger.WithField("service", "runWithinNetns")
Expand All @@ -83,8 +93,10 @@ func main() {
}

func runWithinNetns() (err error) {
// magic file descriptor 3 was passed in from the parent using ExtraFiles
fd := os.NewFile(uintptr(3), "")
listenFDs, _ := strconv.Atoi(os.Getenv("LISTEN_FDS"))

// magic file descriptor 3+listenFDs was passed in from the parent using ExtraFiles
fd := os.NewFile(uintptr(3+listenFDs), "")
defer fd.Close()

log.Debug("waiting for parent")
Expand Down Expand Up @@ -116,6 +128,19 @@ func runWithinNetns() (err error) {
)
}

if listenFDs > 0 {
os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
args = append(args, "-H", "fd://")

dockerd, err := exec.LookPath("dockerd")
if err != nil {
return err
}
argv := []string{dockerd}
argv = append(argv, args...)
return unix.Exec(dockerd, argv, os.Environ())
}

cmd := exec.Command("dockerd", args...)
log.WithField("args", args).Debug("starting dockerd")
cmd.SysProcAttr = &syscall.SysProcAttr{
Expand All @@ -136,7 +161,7 @@ func runWithinNetns() (err error) {
if opts.UserAccessibleSocket {
go func() {
for {
err := os.Chmod("/var/run/docker.sock", 0666)
err := os.Chmod(dockerSocketFN, 0666)

if os.IsNotExist(err) {
time.Sleep(500 * time.Millisecond)
Expand Down Expand Up @@ -181,13 +206,22 @@ func runOutsideNetns() error {
Pdeathsig: syscall.SIGKILL,
Unshareflags: syscall.CLONE_NEWNET,
}
cmd.ExtraFiles = []*os.File{pipeR}
if fds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")); err == nil {
for i := 0; i < fds; i++ {
fmt.Printf("passing fd %d\n", i)
cmd.ExtraFiles = append(cmd.ExtraFiles, os.NewFile(uintptr(3+i), ""))
}
} else {
log.WithError(err).WithField("LISTEN_FDS", os.Getenv("listen_fds")).Warn("no LISTEN_FDS")
}
cmd.ExtraFiles = append(cmd.ExtraFiles, pipeR)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(),
"DOCKERUP_SLIRP4NETNS_SOCKET="+slirpAPI.Name(),
)

err = cmd.Start()
if err != nil {
return err
Expand Down
1 change: 1 addition & 0 deletions components/docker-up/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ require (
github.com/rootless-containers/rootlesskit v0.14.1
github.com/sirupsen/logrus v1.8.1
github.com/spf13/pflag v1.0.5
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
)
1 change: 1 addition & 0 deletions components/supervisor/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/google/uuid v1.1.4
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2
github.com/grpc-ecosystem/grpc-gateway/v2 v2.2.0
github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/prometheus/procfs v0.6.0
github.com/sirupsen/logrus v1.7.0
Expand Down
2 changes: 2 additions & 0 deletions components/supervisor/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f h1:4+gHs0jJFJ06bfN8PshnM6cHcxGjRUVRLo5jndDiKRQ=
github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f/go.mod h1:tHCZHV8b2A90ObojrEAzY0Lb03gxUxjDHr5IJyAh4ew=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
Expand Down
73 changes: 73 additions & 0 deletions components/supervisor/pkg/activation/activation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License-AGPL.txt in the project root for license information.

package activation

import (
"context"
"fmt"
"net"
"os"
"sync"

"github.com/mailru/easygo/netpoll"
)

// Callback is called when a listener is written to. Receivers are expected to close socketFD.
type Callback func(socketFD *os.File) error

// Listen polls on the listener and calls callback when someone writes to it
func Listen(ctx context.Context, l net.Listener, activate Callback) error {
poller, err := netpoll.New(nil)
if err != nil {
return err
}

// Get netpoll descriptor with EventRead|EventEdgeTriggered.
desc, err := netpoll.HandleListener(l, netpoll.EventRead|netpoll.EventEdgeTriggered)
if err != nil {
return err
}

var (
runc = make(chan bool, 1)
once sync.Once
)
poller.Start(desc, func(ev netpoll.Event) {
defer once.Do(func() {
poller.Stop(desc)

close(runc)
})

if ev&netpoll.EventReadHup != 0 {
return
}

runc <- true
})

select {
case run := <-runc:
if !run {
return nil
}
case <-ctx.Done():
return ctx.Err()
}

var f *os.File
switch ll := l.(type) {
case *net.UnixListener:
f, err = ll.File()
case *net.TCPListener:
f, err = ll.File()
default:
return fmt.Errorf("unsuported listener")
}
if err != nil {
return err
}
return activate(f)
}
1 change: 1 addition & 0 deletions components/supervisor/pkg/supervisor/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func (p *GitTokenProvider) GetToken(ctx context.Context, req *api.GetTokenReques
return nil, err
}
gpCmd := exec.Command(gpPath, "preview", "--external", p.workspaceConfig.GitpodHost+"/access-control")
gpCmd = runAsGitpodUser(gpCmd)
err = gpCmd.Start()
if err != nil {
return nil, err
Expand Down
51 changes: 50 additions & 1 deletion components/supervisor/pkg/supervisor/supervisor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/gitpod-io/gitpod/content-service/pkg/initializer"
gitpod "github.com/gitpod-io/gitpod/gitpod-protocol"
"github.com/gitpod-io/gitpod/supervisor/api"
"github.com/gitpod-io/gitpod/supervisor/pkg/activation"
"github.com/gitpod-io/gitpod/supervisor/pkg/dropwriter"
"github.com/gitpod-io/gitpod/supervisor/pkg/ports"
"github.com/gitpod-io/gitpod/supervisor/pkg/terminal"
Expand Down Expand Up @@ -153,6 +154,10 @@ func Run(options ...RunOption) {

termMuxSrv.DefaultWorkdir = cfg.RepoRoot
termMuxSrv.Env = buildIDEEnv(cfg)
termMuxSrv.DefaultCreds = &syscall.Credential{
Uid: 33333,
Gid: 33333,
}

apiServices := []RegisterableService{
&statusService{
Expand Down Expand Up @@ -188,6 +193,7 @@ func Run(options ...RunOption) {
go startContentInit(ctx, cfg, &wg, cstate)
go startAPIEndpoint(ctx, cfg, &wg, apiServices, apiEndpointOpts...)
go taskManager.Run(ctx, &wg)
go socketActivationForDocker(ctx, &wg, termMux)

if !cfg.isHeadless() {
wg.Add(1)
Expand Down Expand Up @@ -286,6 +292,7 @@ func configureGit(cfg *Config) {

for _, s := range settings {
cmd := exec.Command("git", append([]string{"config", "--global"}, s...)...)
cmd = runAsGitpodUser(cmd)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
Expand Down Expand Up @@ -473,6 +480,10 @@ func prepareIDELaunch(cfg *Config) *exec.Cmd {
cmd.SysProcAttr = &syscall.SysProcAttr{
Setpgid: true,
Pdeathsig: syscall.SIGKILL,
Credential: &syscall.Credential{
Uid: 33333,
Gid: 33333,
},
}

// Here we must resist the temptation to "neaten up" the IDE output for headless builds.
Expand Down Expand Up @@ -717,7 +728,7 @@ func terminateChildProcesses() {
func terminateProcess(pid int, privileged bool) {
var err error
if privileged {
cmd := exec.Command("sudo", "kill", "-SIGTERM", fmt.Sprintf("%v", pid))
cmd := exec.Command("kill", "-SIGTERM", fmt.Sprintf("%v", pid))
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
Expand Down Expand Up @@ -767,6 +778,32 @@ func processesWithParent(ppid int) (map[int]int, error) {
return children, nil
}

func socketActivationForDocker(ctx context.Context, wg *sync.WaitGroup, term *terminal.Mux) {
defer wg.Done()

fn := "/var/run/docker.sock"
l, err := net.Listen("unix", fn)
if err != nil {
log.WithError(err).Error("cannot provide Docker activation socket")
}
_ = os.Chown(fn, 33333, 33333)
err = activation.Listen(ctx, l, func(socketFD *os.File) error {
cmd := exec.Command("docker-up")
cmd.Env = append(os.Environ(), "LISTEN_FDS=1")
cmd.ExtraFiles = []*os.File{socketFD}
_, err := term.Start(cmd, terminal.TermOptions{
Annotations: map[string]string{
"supervisor": "true",
},
LogToStdout: true,
})
return err
})
if err != nil {
log.WithError(err).Error("cannot provide Docker activation socket")
}
}

func callDaemonTeardown() {
log.Info("asking ws-daemon to tear down this workspace")
ctx, cancel := context.WithTimeout(context.Background(), timeBudgetDaemonTeardown)
Expand Down Expand Up @@ -810,3 +847,15 @@ func ConnectToInWorkspaceDaemonService(ctx context.Context) (daemon.InWorkspaceS
}
return daemon.NewInWorkspaceServiceClient(conn), conn, nil
}

func runAsGitpodUser(cmd *exec.Cmd) *exec.Cmd {
if cmd.SysProcAttr == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
if cmd.SysProcAttr.Credential == nil {
cmd.SysProcAttr.Credential = &syscall.Credential{}
}
cmd.SysProcAttr.Credential.Gid = 33333
cmd.SysProcAttr.Credential.Uid = 33333
return cmd
}
7 changes: 7 additions & 0 deletions components/supervisor/pkg/terminal/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"path/filepath"
"syscall"
"time"

"github.com/creack/pty"
Expand Down Expand Up @@ -50,6 +51,7 @@ type MuxTerminalService struct {
DefaultWorkdir string
DefaultShell string
Env []string
DefaultCreds *syscall.Credential

api.UnimplementedTerminalServiceServer
}
Expand Down Expand Up @@ -80,6 +82,11 @@ func (srv *MuxTerminalService) OpenWithOptions(ctx context.Context, req *api.Ope
shell = srv.DefaultShell
}
cmd := exec.Command(shell, req.ShellArgs...)
if srv.DefaultCreds != nil {
cmd.SysProcAttr = &syscall.SysProcAttr{
Credential: srv.DefaultCreds,
}
}
if req.Workdir == "" {
cmd.Dir = srv.DefaultWorkdir
} else {
Expand Down
Loading