mirror of
				https://github.com/linuxkit/linuxkit.git
				synced 2025-10-31 01:37:42 +00:00 
			
		
		
		
	Add a runtime config
This adds support for a runtime configuration file that can do: - `mkdir` to make a directory at runtime, eg in `/var` or `/tmp`, to avoid workarounds - `interface` that can create network interfaces in a container or move them - `bindNS` that can bind mount namespaces of an `onboot` container to a file so a service can be started in that namespace. It merges the `service` and `onboot` tools (in `init`) to avoid duplication. This also saves some size for eg LCOW which did not use the `onboot` code in `runc`. Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
		| @@ -20,9 +20,6 @@ RUN git checkout $CONTAINERD_COMMIT | ||||
| RUN make binaries EXTRA_FLAGS="-buildmode pie" EXTRA_LDFLAGS="-extldflags \\\"-fno-PIC -static\\\"" | ||||
| RUN cp bin/containerd bin/ctr bin/containerd-shim /usr/bin/ | ||||
|  | ||||
| ADD cmd /go/src/cmd | ||||
| RUN cd /go/src/cmd/service && ./skanky-vendor.sh $GOPATH/src/github.com/containerd/containerd | ||||
| RUN go-compile.sh /go/src/cmd/service | ||||
| RUN mkdir -p /etc/init.d && ln -s /usr/bin/service /etc/init.d/020-containerd | ||||
|  | ||||
| WORKDIR / | ||||
| @@ -31,7 +28,7 @@ COPY . . | ||||
| FROM scratch | ||||
| ENTRYPOINT [] | ||||
| WORKDIR / | ||||
| COPY --from=alpine /usr/bin/containerd /usr/bin/ctr /usr/bin/containerd-shim /go/bin/service /usr/bin/ | ||||
| COPY --from=alpine /usr/bin/containerd /usr/bin/ctr /usr/bin/containerd-shim /usr/bin/ | ||||
| COPY --from=alpine /etc/containerd/config.toml /etc/containerd/ | ||||
| COPY --from=alpine /usr/share/zoneinfo/UTC /etc/localtime | ||||
| COPY --from=alpine /etc/init.d/ /etc/init.d/ | ||||
|   | ||||
| @@ -1,87 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| // Please note this file is shared between pkg/runc and pkg/containerd | ||||
| // Update it in both places if you make changes | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| func prepare(path string) error { | ||||
| 	// see if we are dealing with a read only or read write container | ||||
| 	if _, err := os.Stat(filepath.Join(path, "lower")); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return prepareRO(path) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	return prepareRW(path) | ||||
| } | ||||
|  | ||||
| func prepareRO(path string) error { | ||||
| 	// make rootfs a mount point, as runc doesn't like it much otherwise | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	if err := syscall.Mount(rootfs, rootfs, "", syscall.MS_BIND, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func prepareRW(path string) error { | ||||
| 	// mount a tmpfs on tmp for upper and workdirs | ||||
| 	// make it private as nothing else should be using this | ||||
| 	tmp := filepath.Join(path, "tmp") | ||||
| 	if err := syscall.Mount("tmpfs", tmp, "tmpfs", 0, "size=10%"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// make it private as nothing else should be using this | ||||
| 	if err := syscall.Mount("", tmp, "", syscall.MS_REMOUNT|syscall.MS_PRIVATE, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	upper := filepath.Join(tmp, "upper") | ||||
| 	// make the mount points | ||||
| 	if err := os.Mkdir(upper, 0755); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	work := filepath.Join(tmp, "work") | ||||
| 	if err := os.Mkdir(work, 0755); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	lower := filepath.Join(path, "lower") | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	opt := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) | ||||
| 	if err := syscall.Mount("overlay", rootfs, "overlay", 0, opt); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // cleanup functions are best efforts only, mainly for rw onboot containers | ||||
| func cleanup(path string) { | ||||
| 	// see if we are dealing with a read only or read write container | ||||
| 	if _, err := os.Stat(filepath.Join(path, "lower")); err != nil { | ||||
| 		cleanupRO(path) | ||||
| 	} else { | ||||
| 		cleanupRW(path) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func cleanupRO(path string) { | ||||
| 	// remove the bind mount | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	_ = syscall.Unmount(rootfs, 0) | ||||
| } | ||||
|  | ||||
| func cleanupRW(path string) { | ||||
| 	// remove the overlay mount | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	_ = os.RemoveAll(rootfs) | ||||
| 	_ = syscall.Unmount(rootfs, 0) | ||||
| 	// remove the tmpfs | ||||
| 	tmp := filepath.Join(path, "tmp") | ||||
| 	_ = os.RemoveAll(tmp) | ||||
| 	_ = syscall.Unmount(tmp, 0) | ||||
| } | ||||
| @@ -1,14 +0,0 @@ | ||||
| #!/bin/sh | ||||
| # | ||||
| # We only need the containerd client and its transitive dependencies | ||||
| # and we conveniently have a checkout already. We actually prefer to | ||||
| # reuse containerd's vendoring for consistency anyway. | ||||
|  | ||||
| set -eu | ||||
| ctrd=$1 | ||||
| cp -r $ctrd/vendor/ vendor/ | ||||
| # We need containerd itself of course | ||||
| mkdir -p vendor/github.com/containerd | ||||
| cp -r $ctrd vendor/github.com/containerd/containerd | ||||
| # Stop go finding nested vendorings | ||||
| rm -rf vendor/github.com/containerd/containerd/vendor | ||||
| @@ -1,17 +1,28 @@ | ||||
| FROM linuxkit/alpine:87a0cd10449d72f374f950004467737dbf440630 AS build | ||||
| RUN apk add --no-cache --initdb alpine-baselayout make gcc musl-dev | ||||
| FROM linuxkit/alpine:0fd732eb9e99c4db0953ae8de23d95de340ab847 AS build | ||||
| RUN apk add --no-cache --initdb alpine-baselayout make gcc musl-dev git linux-headers | ||||
|  | ||||
| ADD usermode-helper.c . | ||||
| RUN make usermode-helper | ||||
| ADD usermode-helper.c ./ | ||||
| RUN LDFLAGS=-static CFLAGS=-Werror make usermode-helper | ||||
|  | ||||
| RUN apk add --no-cache go musl-dev | ||||
| ENV GOPATH=/go PATH=$PATH:/go/bin | ||||
|  | ||||
| COPY init.go /go/src/init/ | ||||
| COPY vendor /go/src/init/vendor/ | ||||
| RUN go-compile.sh /go/src/init/ | ||||
| COPY cmd /go/src/cmd | ||||
| RUN go-compile.sh /go/src/cmd/init | ||||
|  | ||||
| FROM linuxkit/alpine:87a0cd10449d72f374f950004467737dbf440630 AS mirror | ||||
| # checkout containerd for vendoring | ||||
| ENV GOPATH=/go PATH=$PATH:/go/bin | ||||
| # CONTAINERD_REPO and CONTAINERD_COMMIT are defined in linuxkit/alpine | ||||
| RUN mkdir -p $GOPATH/src/github.com/containerd && \ | ||||
|   cd $GOPATH/src/github.com/containerd && \ | ||||
|   git clone $CONTAINERD_REPO | ||||
| WORKDIR $GOPATH/src/github.com/containerd/containerd | ||||
| RUN git checkout $CONTAINERD_COMMIT | ||||
|  | ||||
| RUN cd /go/src/cmd/service && ./skanky-vendor.sh $GOPATH/src/github.com/containerd/containerd | ||||
| RUN go-compile.sh /go/src/cmd/service | ||||
|  | ||||
| FROM linuxkit/alpine:6ed3b299f5243acb6459b4993549c5045e4ad7f4 AS mirror | ||||
| RUN mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/ | ||||
| RUN apk add --no-cache --initdb -p /out alpine-baselayout busybox musl | ||||
|  | ||||
| @@ -23,6 +34,7 @@ ENTRYPOINT [] | ||||
| CMD [] | ||||
| WORKDIR / | ||||
| COPY --from=build /go/bin/init / | ||||
| COPY --from=build /go/bin/service /usr/bin/ | ||||
| COPY --from=build usermode-helper /sbin/ | ||||
| COPY --from=mirror /out/ / | ||||
| COPY etc etc/ | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| IMAGE=init | ||||
| DEPS=init.go vendor.conf usermode-helper.c $(wildcard etc/*) $(wildcard etc/init.d/*) | ||||
| NETWORK=1 | ||||
| DEPS=usermode-helper.c $(wildcard etc/*) $(wildcard etc/init.d/*) $(shell find cmd -type f) | ||||
|  | ||||
| include ../package.mk | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	log "github.com/sirupsen/logrus" | ||||
| ) | ||||
| @@ -13,6 +14,9 @@ const ( | ||||
| 	defaultSocket     = "/run/containerd/containerd.sock" | ||||
| 	defaultPath       = "/containers/services" | ||||
| 	defaultContainerd = "/usr/bin/containerd" | ||||
| 	installPath       = "/usr/bin/service" | ||||
| 	onbootPath        = "/containers/onboot" | ||||
| 	shutdownPath      = "/containers/onshutdown" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| @@ -66,8 +70,17 @@ func main() { | ||||
| 
 | ||||
| 	args := flag.Args() | ||||
| 	if len(args) < 1 { | ||||
| 		systemInitCmd(args) | ||||
| 		os.Exit(0) | ||||
| 		// check if called form startup scripts | ||||
| 		command := os.Args[0] | ||||
| 		switch { | ||||
| 		case strings.Contains(command, "onboot"): | ||||
| 			os.Exit(runcInit(onbootPath)) | ||||
| 		case strings.Contains(command, "onshutdown"): | ||||
| 			os.Exit(runcInit(shutdownPath)) | ||||
| 		case strings.Contains(command, "containerd"): | ||||
| 			systemInitCmd([]string{}) | ||||
| 			os.Exit(0) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	switch args[0] { | ||||
							
								
								
									
										255
									
								
								pkg/init/cmd/service/prepare.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										255
									
								
								pkg/init/cmd/service/prepare.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,255 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"syscall" | ||||
|  | ||||
| 	"github.com/vishvananda/netlink" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	wgPath      = "/usr/bin/wg" | ||||
| 	nsenterPath = "/usr/bin/nsenter-net" | ||||
| ) | ||||
|  | ||||
| // Note these definitions are from moby/tool/src/moby/config.go and should be kept in sync | ||||
|  | ||||
| // Runtime is the type of config processed at runtime, not used to build the OCI spec | ||||
| type Runtime struct { | ||||
| 	Mkdir      []string    `yaml:"mkdir" json:"mkdir,omitempty"` | ||||
| 	Interfaces []Interface `yaml:"interfaces" json:"interfaces,omitempty"` | ||||
| 	BindNS     *Namespaces `yaml:"bindNS" json:"bindNS,omitempty"` | ||||
| } | ||||
|  | ||||
| // Namespaces is the type for configuring paths to bind namespaces | ||||
| type Namespaces struct { | ||||
| 	Cgroup string `yaml:"cgroup" json:"cgroup,omitempty"` | ||||
| 	Ipc    string `yaml:"ipc" json:"ipc,omitempty"` | ||||
| 	Mnt    string `yaml:"mnt" json:"mnt,omitempty"` | ||||
| 	Net    string `yaml:"net" json:"net,omitempty"` | ||||
| 	Pid    string `yaml:"pid" json:"pid,omitempty"` | ||||
| 	User   string `yaml:"user" json:"user,omitempty"` | ||||
| 	Uts    string `yaml:"uts" json:"uts,omitempty"` | ||||
| } | ||||
|  | ||||
| // Interface is the runtime config for network interfaces | ||||
| type Interface struct { | ||||
| 	Name         string `yaml:"name" json:"name,omitempty"` | ||||
| 	Add          string `yaml:"add" json:"add,omitempty"` | ||||
| 	Peer         string `yaml:"peer" json:"peer,omitempty"` | ||||
| 	CreateInRoot bool   `yaml:"createInRoot" json:"createInRoot"` | ||||
| } | ||||
|  | ||||
| func getRuntimeConfig(path string) Runtime { | ||||
| 	var runtime Runtime | ||||
| 	conf, err := ioutil.ReadFile(filepath.Join(path, "runtime.json")) | ||||
| 	if err != nil { | ||||
| 		// if it does not exist it is fine to return an empty runtime, to not do anything | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return runtime | ||||
| 		} | ||||
| 		log.Fatalf("Cannot read runtime config: %v", err) | ||||
| 	} | ||||
| 	if err := json.Unmarshal(conf, &runtime); err != nil { | ||||
| 		log.Fatalf("Cannot parse runtime config: %v", err) | ||||
| 	} | ||||
| 	return runtime | ||||
| } | ||||
|  | ||||
| // prepareFilesystem sets up the mounts, before the container is created | ||||
| func prepareFilesystem(path string, runtime Runtime) error { | ||||
| 	// execute the runtime config that should be done up front | ||||
| 	for _, dir := range runtime.Mkdir { | ||||
| 		// in future we may need to change the structure to set mode, ownership | ||||
| 		var mode os.FileMode = 0755 | ||||
| 		err := os.MkdirAll(dir, mode) | ||||
| 		if err != nil { | ||||
| 			return fmt.Errorf("Cannot create directory %s: %v", dir, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// see if we are dealing with a read only or read write container | ||||
| 	if _, err := os.Stat(filepath.Join(path, "lower")); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return prepareRO(path) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	return prepareRW(path) | ||||
| } | ||||
|  | ||||
| func prepareRO(path string) error { | ||||
| 	// make rootfs a mount point, as runc doesn't like it much otherwise | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	if err := syscall.Mount(rootfs, rootfs, "", syscall.MS_BIND, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func prepareRW(path string) error { | ||||
| 	// mount a tmpfs on tmp for upper and workdirs | ||||
| 	// make it private as nothing else should be using this | ||||
| 	tmp := filepath.Join(path, "tmp") | ||||
| 	if err := syscall.Mount("tmpfs", tmp, "tmpfs", 0, "size=10%"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// make it private as nothing else should be using this | ||||
| 	if err := syscall.Mount("", tmp, "", syscall.MS_REMOUNT|syscall.MS_PRIVATE, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	upper := filepath.Join(tmp, "upper") | ||||
| 	// make the mount points | ||||
| 	if err := os.Mkdir(upper, 0755); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	work := filepath.Join(tmp, "work") | ||||
| 	if err := os.Mkdir(work, 0755); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	lower := filepath.Join(path, "lower") | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	opt := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) | ||||
| 	if err := syscall.Mount("overlay", rootfs, "overlay", 0, opt); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // bind mount a namespace file | ||||
| func bindNS(ns string, path string, pid int) error { | ||||
| 	if path == "" { | ||||
| 		return nil | ||||
| 	} | ||||
| 	// the path and file need to exist for the bind to succeed, so try to create | ||||
| 	dir := filepath.Dir(path) | ||||
| 	if err := os.MkdirAll(dir, 0755); err != nil { | ||||
| 		return fmt.Errorf("Cannot create leading directories %s for bind mount destination: %v", dir, err) | ||||
| 	} | ||||
| 	fi, err := os.Create(path) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Cannot create a mount point for namespace bind at %s: %v", path, err) | ||||
| 	} | ||||
| 	if err := fi.Close(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := syscall.Mount(fmt.Sprintf("/proc/%d/ns/%s", pid, ns), path, "", syscall.MS_BIND, ""); err != nil { | ||||
| 		return fmt.Errorf("Failed to bind %s namespace at %s: %v", ns, path, err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // prepareProcess sets up anything that needs to be done after the container process is created, but before it runs | ||||
| // for example networking | ||||
| func prepareProcess(pid int, runtime Runtime) error { | ||||
| 	for _, iface := range runtime.Interfaces { | ||||
| 		if iface.Name == "" { | ||||
| 			return fmt.Errorf("Interface requires a name") | ||||
| 		} | ||||
|  | ||||
| 		var link netlink.Link | ||||
| 		var ns interface{} = netlink.NsPid(pid) | ||||
| 		var move bool | ||||
| 		var err error | ||||
|  | ||||
| 		if iface.Peer != "" && iface.Add == "" { | ||||
| 			// must be a veth if specify peer | ||||
| 			iface.Add = "veth" | ||||
| 		} | ||||
|  | ||||
| 		// if create in root is set, create in root namespace first, then move | ||||
| 		// also do the same for a veth pair | ||||
| 		if iface.CreateInRoot || iface.Add == "veth" { | ||||
| 			ns = nil | ||||
| 			move = true | ||||
| 		} | ||||
|  | ||||
| 		if iface.Add != "" { | ||||
| 			switch iface.Add { | ||||
| 			case "veth": | ||||
| 				if iface.Peer == "" { | ||||
| 					return fmt.Errorf("Creating a veth pair %s requires a peer to be set", iface.Name) | ||||
| 				} | ||||
| 				la := netlink.LinkAttrs{Name: iface.Name, Namespace: ns} | ||||
| 				link = &netlink.Veth{LinkAttrs: la, PeerName: iface.Peer} | ||||
| 			default: | ||||
| 				// no special creation options needed | ||||
| 				la := netlink.LinkAttrs{Name: iface.Name, Namespace: ns} | ||||
| 				link = &netlink.GenericLink{la, iface.Add} | ||||
| 			} | ||||
| 			if err := netlink.LinkAdd(link); err != nil { | ||||
| 				return fmt.Errorf("Link add %s of type %s failed: %v", iface.Name, iface.Add, err) | ||||
| 			} | ||||
| 			fmt.Fprintf(os.Stderr, "Created interface %s type %s\n", iface.Name, iface.Add) | ||||
| 		} else { | ||||
| 			// find existing interface | ||||
| 			link, err = netlink.LinkByName(iface.Name) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Cannot find interface %s: %v", iface.Name, err) | ||||
| 			} | ||||
| 			// then move into namespace | ||||
| 			move = true | ||||
| 		} | ||||
| 		if move { | ||||
| 			if err := netlink.LinkSetNsPid(link, int(pid)); err != nil { | ||||
| 				return fmt.Errorf("Cannot move interface %s into namespace: %v", iface.Name, err) | ||||
| 			} | ||||
| 			fmt.Fprintf(os.Stderr, "Moved interface %s to pid %d\n", iface.Name, pid) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if runtime.BindNS != nil { | ||||
| 		binds := []struct { | ||||
| 			ns   string | ||||
| 			path string | ||||
| 		}{ | ||||
| 			{"cgroup", runtime.BindNS.Cgroup}, | ||||
| 			{"ipc", runtime.BindNS.Ipc}, | ||||
| 			{"mnt", runtime.BindNS.Mnt}, | ||||
| 			{"net", runtime.BindNS.Net}, | ||||
| 			{"pid", runtime.BindNS.Pid}, | ||||
| 			{"user", runtime.BindNS.User}, | ||||
| 			{"uts", runtime.BindNS.Uts}, | ||||
| 		} | ||||
|  | ||||
| 		for _, b := range binds { | ||||
| 			if err := bindNS(b.ns, b.path, pid); err != nil { | ||||
| 				return err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // cleanup functions are best efforts only, mainly for rw onboot containers | ||||
| func cleanup(path string) { | ||||
| 	// see if we are dealing with a read only or read write container | ||||
| 	if _, err := os.Stat(filepath.Join(path, "lower")); err != nil { | ||||
| 		cleanupRO(path) | ||||
| 	} else { | ||||
| 		cleanupRW(path) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func cleanupRO(path string) { | ||||
| 	// remove the bind mount | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	_ = syscall.Unmount(rootfs, 0) | ||||
| } | ||||
|  | ||||
| func cleanupRW(path string) { | ||||
| 	// remove the overlay mount | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	_ = os.RemoveAll(rootfs) | ||||
| 	_ = syscall.Unmount(rootfs, 0) | ||||
| 	// remove the tmpfs | ||||
| 	tmp := filepath.Join(path, "tmp") | ||||
| 	_ = os.RemoveAll(tmp) | ||||
| 	_ = syscall.Unmount(tmp, 0) | ||||
| } | ||||
							
								
								
									
										111
									
								
								pkg/init/cmd/service/runc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								pkg/init/cmd/service/runc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"github.com/containerd/containerd/sys" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	runcBinary = "/usr/bin/runc" | ||||
| ) | ||||
|  | ||||
| func runcInit(rootPath string) int { | ||||
| 	// do nothing if the path does not exist | ||||
| 	if _, err := os.Stat(rootPath); err != nil && os.IsNotExist(err) { | ||||
| 		return 0 | ||||
| 	} | ||||
|  | ||||
| 	// get files; note ReadDir already sorts them | ||||
| 	files, err := ioutil.ReadDir(rootPath) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Cannot read files in %s: %v", rootPath, err) | ||||
| 	} | ||||
|  | ||||
| 	tmpdir, err := ioutil.TempDir("", filepath.Base(rootPath)) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Cannot create temporary directory: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	// need to set ourselves as a child subreaper or we cannot wait for runc as reparents to init | ||||
| 	if err := sys.SetSubreaper(1); err != nil { | ||||
| 		log.Fatalf("Cannot set as subreaper: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	status := 0 | ||||
|  | ||||
| 	for _, file := range files { | ||||
| 		name := file.Name() | ||||
| 		path := filepath.Join(rootPath, name) | ||||
|  | ||||
| 		runtimeConfig := getRuntimeConfig(path) | ||||
|  | ||||
| 		if err := prepareFilesystem(path, runtimeConfig); err != nil { | ||||
| 			log.Printf("Error preparing %s: %v", name, err) | ||||
| 			status = 1 | ||||
| 			continue | ||||
| 		} | ||||
| 		pidfile := filepath.Join(tmpdir, name) | ||||
| 		cmd := exec.Command(runcBinary, "create", "--bundle", path, "--pid-file", pidfile, name) | ||||
| 		cmd.Stdout = os.Stdout | ||||
| 		cmd.Stderr = os.Stderr | ||||
| 		if err := cmd.Run(); err != nil { | ||||
| 			log.Printf("Error creating %s: %v", name, err) | ||||
| 			status = 1 | ||||
| 			// skip cleanup on error for debug | ||||
| 			continue | ||||
| 		} | ||||
| 		pf, err := ioutil.ReadFile(pidfile) | ||||
| 		if err != nil { | ||||
| 			log.Printf("Cannot read pidfile: %v", err) | ||||
| 			status = 1 | ||||
| 			continue | ||||
| 		} | ||||
| 		pid, err := strconv.Atoi(string(pf)) | ||||
| 		if err != nil { | ||||
| 			log.Printf("Cannot parse pid from pidfile: %v", err) | ||||
| 			status = 1 | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		if err := prepareProcess(pid, runtimeConfig); err != nil { | ||||
| 			log.Printf("Cannot prepare process: %v", err) | ||||
| 			status = 1 | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		waitFor := make(chan *os.ProcessState) | ||||
| 		go func() { | ||||
| 			// never errors in Unix | ||||
| 			p, _ := os.FindProcess(pid) | ||||
| 			state, err := p.Wait() | ||||
| 			if err != nil { | ||||
| 				log.Printf("Process wait error: %v", err) | ||||
| 			} | ||||
| 			waitFor <- state | ||||
| 		}() | ||||
|  | ||||
| 		cmd = exec.Command(runcBinary, "start", name) | ||||
| 		cmd.Stdout = os.Stdout | ||||
| 		cmd.Stderr = os.Stderr | ||||
| 		if err := cmd.Run(); err != nil { | ||||
| 			log.Printf("Error starting %s: %v", name, err) | ||||
| 			status = 1 | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		_ = <-waitFor | ||||
|  | ||||
| 		cleanup(path) | ||||
| 		_ = os.Remove(pidfile) | ||||
| 	} | ||||
|  | ||||
| 	_ = os.RemoveAll(tmpdir) | ||||
|  | ||||
| 	return status | ||||
| } | ||||
| @@ -55,11 +55,15 @@ func startCmd(args []string) { | ||||
| 	log.Debugf("Started %s pid %d", id, pid) | ||||
| } | ||||
| 
 | ||||
| func start(service, sock, path, dumpSpec string) (string, uint32, string, error) { | ||||
| 	rootfs := filepath.Join(path, service, "rootfs") | ||||
| func start(service, sock, basePath, dumpSpec string) (string, uint32, string, error) { | ||||
| 	path := filepath.Join(basePath, service) | ||||
| 
 | ||||
| 	if err := prepare(filepath.Join(path, service)); err != nil { | ||||
| 		return "", 0, "preparing rootfs", err | ||||
| 	runtimeConfig := getRuntimeConfig(path) | ||||
| 
 | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 
 | ||||
| 	if err := prepareFilesystem(path, runtimeConfig); err != nil { | ||||
| 		return "", 0, "preparing filesystem", err | ||||
| 	} | ||||
| 
 | ||||
| 	client, err := containerd.New(sock) | ||||
| @@ -70,7 +74,7 @@ func start(service, sock, path, dumpSpec string) (string, uint32, string, error) | ||||
| 	ctx := namespaces.WithNamespace(context.Background(), "default") | ||||
| 
 | ||||
| 	var spec *specs.Spec | ||||
| 	specf, err := os.Open(filepath.Join(path, service, "config.json")) | ||||
| 	specf, err := os.Open(filepath.Join(path, "config.json")) | ||||
| 	if err != nil { | ||||
| 		return "", 0, "failed to read service spec", err | ||||
| 	} | ||||
| @@ -119,6 +123,10 @@ func start(service, sock, path, dumpSpec string) (string, uint32, string, error) | ||||
| 		return "", 0, "failed to create task", err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := prepareProcess(int(task.Pid()), runtimeConfig); err != nil { | ||||
| 		return "", 0, "preparing process", err | ||||
| 	} | ||||
| 
 | ||||
| 	if err := task.Start(ctx); err != nil { | ||||
| 		// Don't destroy the container here so it can be inspected for debugging. | ||||
| 		return "", 0, "failed to start task", err | ||||
| @@ -20,14 +20,12 @@ RUN git checkout $RUNC_COMMIT | ||||
| RUN make static BUILDTAGS="seccomp" EXTRA_FLAGS="-buildmode pie" EXTRA_LDFLAGS="-extldflags \\\"-fno-PIC -static\\\"" | ||||
| RUN cp runc /usr/bin/ | ||||
|  | ||||
| ADD cmd /go/src/cmd | ||||
| RUN go-compile.sh /go/src/cmd/onboot | ||||
| RUN mkdir -p /etc/init.d && ln -s /usr/bin/onboot /etc/init.d/010-onboot | ||||
| RUN mkdir -p /etc/shutdown.d && ln -s /usr/bin/onboot /etc/shutdown.d/010-onshutdown | ||||
| RUN mkdir -p /etc/init.d && ln -s /usr/bin/service /etc/init.d/010-onboot | ||||
| RUN mkdir -p /etc/shutdown.d && ln -s /usr/bin/service /etc/shutdown.d/010-onshutdown | ||||
|  | ||||
| FROM scratch | ||||
| WORKDIR / | ||||
| ENTRYPOINT [] | ||||
| COPY --from=alpine /usr/bin/runc /go/bin/onboot /usr/bin/ | ||||
| COPY --from=alpine /usr/bin/runc /usr/bin/ | ||||
| COPY --from=alpine /etc/init.d/ /etc/init.d/ | ||||
| COPY --from=alpine /etc/shutdown.d/ /etc/shutdown.d/ | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| IMAGE=runc | ||||
| NETWORK=1 | ||||
| DEPS=$(wildcard cmd/onboot/*.go) | ||||
|  | ||||
| include ../package.mk | ||||
|   | ||||
| @@ -1,66 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"io/ioutil" | ||||
| 	"log" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	runcBinary   = "/usr/bin/runc" | ||||
| 	onbootPath   = "/containers/onboot" | ||||
| 	shutdownPath = "/containers/onshutdown" | ||||
| ) | ||||
|  | ||||
| func main() { | ||||
| 	// try to work out how we are being called | ||||
| 	command := os.Args[0] | ||||
| 	if len(os.Args) > 1 { | ||||
| 		command = os.Args[1] | ||||
| 	} | ||||
| 	var path = onbootPath | ||||
| 	switch { | ||||
| 	case strings.Contains(command, "boot"): | ||||
| 		path = onbootPath | ||||
| 	case strings.Contains(command, "shutdown"): | ||||
| 		path = shutdownPath | ||||
| 	} | ||||
|  | ||||
| 	// do nothing if the path does not exist | ||||
| 	if _, err := os.Stat(path); err != nil && os.IsNotExist(err) { | ||||
| 		os.Exit(0) | ||||
| 	} | ||||
|  | ||||
| 	// get files; note ReadDir already sorts them | ||||
| 	files, err := ioutil.ReadDir(path) | ||||
| 	if err != nil { | ||||
| 		log.Fatalf("Cannot read files in %s: %v", path, err) | ||||
| 	} | ||||
|  | ||||
| 	status := 0 | ||||
|  | ||||
| 	for _, file := range files { | ||||
| 		name := file.Name() | ||||
| 		fullPath := filepath.Join(path, name) | ||||
| 		if err := prepare(fullPath); err != nil { | ||||
| 			log.Printf("Error preparing %s: %v", name, err) | ||||
| 			status = 1 | ||||
| 			continue | ||||
| 		} | ||||
| 		cmd := exec.Command(runcBinary, "run", "--bundle", fullPath, name) | ||||
| 		cmd.Stdout = os.Stdout | ||||
| 		cmd.Stderr = os.Stderr | ||||
| 		if err := cmd.Run(); err != nil { | ||||
| 			log.Printf("Error running %s: %v", name, err) | ||||
| 			status = 1 | ||||
| 		} else { | ||||
| 			// do not clean up on error to make debug easier | ||||
| 			cleanup(fullPath) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	os.Exit(status) | ||||
| } | ||||
| @@ -1,87 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| // Please note this file is shared between pkg/runc and pkg/containerd | ||||
| // Update it in both places if you make changes | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"syscall" | ||||
| ) | ||||
|  | ||||
| func prepare(path string) error { | ||||
| 	// see if we are dealing with a read only or read write container | ||||
| 	if _, err := os.Stat(filepath.Join(path, "lower")); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return prepareRO(path) | ||||
| 		} | ||||
| 		return err | ||||
| 	} | ||||
| 	return prepareRW(path) | ||||
| } | ||||
|  | ||||
| func prepareRO(path string) error { | ||||
| 	// make rootfs a mount point, as runc doesn't like it much otherwise | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	if err := syscall.Mount(rootfs, rootfs, "", syscall.MS_BIND, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func prepareRW(path string) error { | ||||
| 	// mount a tmpfs on tmp for upper and workdirs | ||||
| 	// make it private as nothing else should be using this | ||||
| 	tmp := filepath.Join(path, "tmp") | ||||
| 	if err := syscall.Mount("tmpfs", tmp, "tmpfs", 0, "size=10%"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// make it private as nothing else should be using this | ||||
| 	if err := syscall.Mount("", tmp, "", syscall.MS_REMOUNT|syscall.MS_PRIVATE, ""); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	upper := filepath.Join(tmp, "upper") | ||||
| 	// make the mount points | ||||
| 	if err := os.Mkdir(upper, 0755); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	work := filepath.Join(tmp, "work") | ||||
| 	if err := os.Mkdir(work, 0755); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	lower := filepath.Join(path, "lower") | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	opt := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) | ||||
| 	if err := syscall.Mount("overlay", rootfs, "overlay", 0, opt); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // cleanup functions are best efforts only, mainly for rw onboot containers | ||||
| func cleanup(path string) { | ||||
| 	// see if we are dealing with a read only or read write container | ||||
| 	if _, err := os.Stat(filepath.Join(path, "lower")); err != nil { | ||||
| 		cleanupRO(path) | ||||
| 	} else { | ||||
| 		cleanupRW(path) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func cleanupRO(path string) { | ||||
| 	// remove the bind mount | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	_ = syscall.Unmount(rootfs, 0) | ||||
| } | ||||
|  | ||||
| func cleanupRW(path string) { | ||||
| 	// remove the overlay mount | ||||
| 	rootfs := filepath.Join(path, "rootfs") | ||||
| 	_ = os.RemoveAll(rootfs) | ||||
| 	_ = syscall.Unmount(rootfs, 0) | ||||
| 	// remove the tmpfs | ||||
| 	tmp := filepath.Join(path, "tmp") | ||||
| 	_ = os.RemoveAll(tmp) | ||||
| 	_ = syscall.Unmount(tmp, 0) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user