Skip to content

Support custom docker daemon args #8435

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

Merged
merged 9 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from 8 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
1 change: 1 addition & 0 deletions components/docker-up/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ bin
docker-compose
docker.tgz
slirp4netns
runc
1 change: 1 addition & 0 deletions components/docker-up/dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ DOCKER_COMPOSE_VERSION=1.29.2

curl -o docker.tgz -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz
curl -o docker-compose -fsSL https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-Linux-x86_64
curl -o runc -fsSL https://github.com/opencontainers/runc/releases/download/v1.1.0/runc.amd64
240 changes: 230 additions & 10 deletions components/docker-up/docker-up/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ package main

import (
"archive/tar"
"bufio"
"compress/gzip"
"context"
"embed"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
Expand All @@ -32,6 +35,8 @@ import (

var log *logrus.Entry

const DaemonArgs = "DOCKER_DAEMON_ARGS"

var opts struct {
RuncFacade bool
BinDir string
Expand All @@ -43,8 +48,12 @@ var opts struct {

//go:embed docker.tgz
//go:embed docker-compose
//go:embed runc
var binaries embed.FS

// ensure apt update is run only once
var aptUpdated = false

const (
dockerSocketFN = "/var/run/docker.sock"
)
Expand Down Expand Up @@ -106,6 +115,11 @@ func runWithinNetns() (err error) {
)
}

args, err = setUserArgs(args)
if err != nil {
return xerrors.Errorf("cannot add user supplied docker args: %w", err)
}

if listenFDs > 0 {
os.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid()))
args = append(args, "-H", "fd://")
Expand Down Expand Up @@ -161,10 +175,91 @@ func runWithinNetns() (err error) {
return nil
}

var allowedDockerArgs = map[string]string{
"remap-user": "userns-remap",
}

func setUserArgs(args []string) ([]string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function seemed simpler if it was dedicated to crate args from env vars from the user that were converted to dockerd args. In other words, instead of doing both generation and addition, why not just concentrate on generation?

Suggested change
func setUserArgs(args []string) ([]string, error) {
func userArgs() ([]string, error) {
args := []string{}

And,

	uargs, err := userArgs()
	if err != nil {
		return xerrors.Errorf("cannot add user supplied docker args: %w", err)
	}
	args = append(args, uargs...)

userArgs, exists := os.LookupEnv(DaemonArgs)
if !exists {
return args, nil
}

var providedDockerArgs map[string]string
if err := json.Unmarshal([]byte(userArgs), &providedDockerArgs); err != nil {
return nil, xerrors.Errorf("unable to deserialize docker args: %w", err)
}

for userArg, userValue := range providedDockerArgs {
mapped, exists := allowedDockerArgs[userArg]
if !exists {
continue
}

if userArg == "remap-user" {
id, err := strconv.Atoi(userValue)
if err != nil {
return nil, err
}

for _, f := range []string{"/etc/subuid", "/etc/subgid"} {
err := adaptSubid(f, id)
if err != nil {
return nil, xerrors.Errorf("could not adapt subid files: %w", err)
}
}

args = append(args, "--"+mapped, "gitpod")
} else {
args = append(args, "--"+userArg, userValue)
}
}

return args, nil
}

func adaptSubid(oldfile string, id int) error {
uid, err := os.Open(oldfile)
if err != nil {
return err
}

newfile, err := os.Create(oldfile + ".new")
if err != nil {
return err
}

if id != 0 {
newfile.WriteString(fmt.Sprintf("gitpod:%d:%d\n", 1, id))
newfile.WriteString("gitpod:33333:1\n")

} else {
newfile.WriteString("gitpod:33333:1\n")
newfile.WriteString(fmt.Sprintf("gitpod:%d:%d\n", 1, 33332))
newfile.WriteString(fmt.Sprintf("gitpod:%d:%d\n", 33334, 32200))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about this? I couldn't get the point what 32200 mean. What does it mean?

Suggested change
if id != 0 {
newfile.WriteString(fmt.Sprintf("gitpod:%d:%d\n", 1, id))
newfile.WriteString("gitpod:33333:1\n")
} else {
newfile.WriteString("gitpod:33333:1\n")
newfile.WriteString(fmt.Sprintf("gitpod:%d:%d\n", 1, 33332))
newfile.WriteString(fmt.Sprintf("gitpod:%d:%d\n", 33334, 32200))
}
gitpodUserId := 33333
mappingFmt := func(username string, id int, size int) string { return fmt.Sprintf("%s:%d:%d\n", username, id, size) }
if id != 0 {
newfile.WriteString(mappingFmt("gitpod", 1, id))
newfile.WriteString(mappingFmt("gitpod", gitpodUserId, 1))
} else {
newfile.WriteString(mappingFmt("gitpod", gitpodUserId, 1))
newfile.WriteString(mappingFmt("gitpod", 1, gitpodUserId-1))
newfile.WriteString(mappingFmt("gitpod", gitpodUserId+1, 33200))
}


uidScanner := bufio.NewScanner(uid)
for uidScanner.Scan() {
l := uidScanner.Text()
if !strings.HasPrefix(l, "gitpod") {
newfile.WriteString(l + "\n")
}
}

if err = os.Rename(newfile.Name(), oldfile); err != nil {
return err
}

return nil
}

var prerequisites = map[string]func() error{
"dockerd": installDocker,
"docker-compose": installDockerCompose,
"iptables": installIptables,
"uidmap": installUidMap,
"runcV1.1.0": installRunc,
}

func ensurePrerequisites() error {
Expand Down Expand Up @@ -253,9 +348,14 @@ func installDockerCompose() error {
}

func installIptables() error {
pth, _ := exec.LookPath("apt-get")
err := installPackages("iptables", "xz-utils")
if err != nil {
return xerrors.Errorf("could not install iptables: %w", err)
}

pth, _ := exec.LookPath("apk")
if pth != "" {
cmd := exec.Command("/bin/sh", "-c", "apt-get update && apt-get install -y iptables xz-utils")
cmd := exec.Command("/bin/sh", "-c", "apk add --no-cache iptables xz")
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
Expand All @@ -266,22 +366,142 @@ func installIptables() error {
return cmd.Run()
}

pth, _ = exec.LookPath("apk")
if pth != "" {
cmd := exec.Command("/bin/sh", "-c", "apk add --no-cache iptables xz")
// the container is not debian/ubuntu/alpine
log.WithField("command", "dockerd").Warn("Please install dockerd dependencies: iptables")
return nil
}

func installUidMap() error {
_, exists := os.LookupEnv(DaemonArgs)
if !exists {
return nil
}

needInstall := false
if _, err := exec.LookPath("newuidmap"); err != nil {
needInstall = true
}

if _, err := exec.LookPath("newgidmap"); err != nil {
needInstall = true
}

if !needInstall {
return nil
}

err := installPackages("uidmap")
if err != nil {
return xerrors.Errorf("could not install uidmap: %w", err)
}

return nil
}

func installRunc() error {
_, exists := os.LookupEnv(DaemonArgs)
if !exists {
return nil
}

runc, _ := exec.LookPath("runc")
if runc != "" {
// if the required version or a more recent one is already
// installed do nothing
if !needInstallRunc() {
return nil
}
} else {
runc = "/bin/runc"
}

err := installBinary("runc", runc)
if err != nil {
return xerrors.Errorf("could not install runc: %w", err)
}

return nil
}

func needInstallRunc() bool {
cmd := exec.Command("runc", "--version")
output, err := cmd.Output()
if err != nil {
return true
}

major, minor, err := detectRuncVersion(string(output))
if err != nil {
return true
}

return major < 1 || major == 1 && minor < 1
}

func detectRuncVersion(output string) (major, minor int, err error) {
versionInfo := strings.Split(output, "\n")
for _, l := range versionInfo {
if !strings.HasPrefix(l, "runc version") {
continue
}

l = strings.TrimPrefix(l, "runc version")
l = strings.TrimSpace(l)

n := strings.Split(l, ".")
if len(n) < 2 {
return 0, 0, xerrors.Errorf("could not parse %s", l)
}

major, err = strconv.Atoi(n[0])
if err != nil {
return 0, 0, xerrors.Errorf("could not parse major %s: %w", n[0])
}

minor, err = strconv.Atoi(n[1])
if err != nil {
return 0, 0, xerrors.Errorf("could not parse minor %s: %w", n[1])
}

return major, minor, nil
}

return 0, 0, xerrors.Errorf("could not detect runc version")
}

func installPackages(packages ...string) error {
apt, _ := exec.LookPath("apt-get")
if apt != "" {
cmd := exec.Command("/bin/sh", "-c")

var installCommand string
if !aptUpdated {
installCommand = "apt-get update && "
}

installCommand = installCommand + "apt-get install -y"
for _, p := range packages {
installCommand = installCommand + " " + p
}

cmd.Args = append(cmd.Args, installCommand)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.SysProcAttr = &syscall.SysProcAttr{
Pdeathsig: syscall.SIGKILL,
}

return cmd.Run()
}
err := cmd.Run()
if err != nil {
return err
}

// the container is not debian/ubuntu/alpine
log.WithField("command", "dockerd").Warn("Please install dockerd dependencies: iptables")
return nil
aptUpdated = true
return nil
} else {
return xerrors.Errorf("apt-get is not available")
}
}

func installBinary(name, dst string) error {
Expand Down