mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-22 02:21:34 +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:
parent
df16f6fb9e
commit
0c81ce19e8
@ -20,9 +20,6 @@ RUN git checkout $CONTAINERD_COMMIT
|
|||||||
RUN make binaries EXTRA_FLAGS="-buildmode pie" EXTRA_LDFLAGS="-extldflags \\\"-fno-PIC -static\\\""
|
RUN make binaries EXTRA_FLAGS="-buildmode pie" EXTRA_LDFLAGS="-extldflags \\\"-fno-PIC -static\\\""
|
||||||
RUN cp bin/containerd bin/ctr bin/containerd-shim /usr/bin/
|
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
|
RUN mkdir -p /etc/init.d && ln -s /usr/bin/service /etc/init.d/020-containerd
|
||||||
|
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
@ -31,7 +28,7 @@ COPY . .
|
|||||||
FROM scratch
|
FROM scratch
|
||||||
ENTRYPOINT []
|
ENTRYPOINT []
|
||||||
WORKDIR /
|
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 /etc/containerd/config.toml /etc/containerd/
|
||||||
COPY --from=alpine /usr/share/zoneinfo/UTC /etc/localtime
|
COPY --from=alpine /usr/share/zoneinfo/UTC /etc/localtime
|
||||||
COPY --from=alpine /etc/init.d/ /etc/init.d/
|
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
|
FROM linuxkit/alpine:0fd732eb9e99c4db0953ae8de23d95de340ab847 AS build
|
||||||
RUN apk add --no-cache --initdb alpine-baselayout make gcc musl-dev
|
RUN apk add --no-cache --initdb alpine-baselayout make gcc musl-dev git linux-headers
|
||||||
|
|
||||||
ADD usermode-helper.c .
|
ADD usermode-helper.c ./
|
||||||
RUN make usermode-helper
|
RUN LDFLAGS=-static CFLAGS=-Werror make usermode-helper
|
||||||
|
|
||||||
RUN apk add --no-cache go musl-dev
|
RUN apk add --no-cache go musl-dev
|
||||||
ENV GOPATH=/go PATH=$PATH:/go/bin
|
ENV GOPATH=/go PATH=$PATH:/go/bin
|
||||||
|
|
||||||
COPY init.go /go/src/init/
|
COPY cmd /go/src/cmd
|
||||||
COPY vendor /go/src/init/vendor/
|
RUN go-compile.sh /go/src/cmd/init
|
||||||
RUN go-compile.sh /go/src/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 mkdir -p /out/etc/apk && cp -r /etc/apk/* /out/etc/apk/
|
||||||
RUN apk add --no-cache --initdb -p /out alpine-baselayout busybox musl
|
RUN apk add --no-cache --initdb -p /out alpine-baselayout busybox musl
|
||||||
|
|
||||||
@ -23,6 +34,7 @@ ENTRYPOINT []
|
|||||||
CMD []
|
CMD []
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
COPY --from=build /go/bin/init /
|
COPY --from=build /go/bin/init /
|
||||||
|
COPY --from=build /go/bin/service /usr/bin/
|
||||||
COPY --from=build usermode-helper /sbin/
|
COPY --from=build usermode-helper /sbin/
|
||||||
COPY --from=mirror /out/ /
|
COPY --from=mirror /out/ /
|
||||||
COPY etc etc/
|
COPY etc etc/
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
IMAGE=init
|
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
|
include ../package.mk
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -13,6 +14,9 @@ const (
|
|||||||
defaultSocket = "/run/containerd/containerd.sock"
|
defaultSocket = "/run/containerd/containerd.sock"
|
||||||
defaultPath = "/containers/services"
|
defaultPath = "/containers/services"
|
||||||
defaultContainerd = "/usr/bin/containerd"
|
defaultContainerd = "/usr/bin/containerd"
|
||||||
|
installPath = "/usr/bin/service"
|
||||||
|
onbootPath = "/containers/onboot"
|
||||||
|
shutdownPath = "/containers/onshutdown"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -66,9 +70,18 @@ func main() {
|
|||||||
|
|
||||||
args := flag.Args()
|
args := flag.Args()
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
systemInitCmd(args)
|
// 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)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "start":
|
case "start":
|
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)
|
log.Debugf("Started %s pid %d", id, pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(service, sock, path, dumpSpec string) (string, uint32, string, error) {
|
func start(service, sock, basePath, dumpSpec string) (string, uint32, string, error) {
|
||||||
rootfs := filepath.Join(path, service, "rootfs")
|
path := filepath.Join(basePath, service)
|
||||||
|
|
||||||
if err := prepare(filepath.Join(path, service)); err != nil {
|
runtimeConfig := getRuntimeConfig(path)
|
||||||
return "", 0, "preparing rootfs", err
|
|
||||||
|
rootfs := filepath.Join(path, "rootfs")
|
||||||
|
|
||||||
|
if err := prepareFilesystem(path, runtimeConfig); err != nil {
|
||||||
|
return "", 0, "preparing filesystem", err
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := containerd.New(sock)
|
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")
|
ctx := namespaces.WithNamespace(context.Background(), "default")
|
||||||
|
|
||||||
var spec *specs.Spec
|
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 {
|
if err != nil {
|
||||||
return "", 0, "failed to read service spec", err
|
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
|
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 {
|
if err := task.Start(ctx); err != nil {
|
||||||
// Don't destroy the container here so it can be inspected for debugging.
|
// Don't destroy the container here so it can be inspected for debugging.
|
||||||
return "", 0, "failed to start task", err
|
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 make static BUILDTAGS="seccomp" EXTRA_FLAGS="-buildmode pie" EXTRA_LDFLAGS="-extldflags \\\"-fno-PIC -static\\\""
|
||||||
RUN cp runc /usr/bin/
|
RUN cp runc /usr/bin/
|
||||||
|
|
||||||
ADD cmd /go/src/cmd
|
RUN mkdir -p /etc/init.d && ln -s /usr/bin/service /etc/init.d/010-onboot
|
||||||
RUN go-compile.sh /go/src/cmd/onboot
|
RUN mkdir -p /etc/shutdown.d && ln -s /usr/bin/service /etc/shutdown.d/010-onshutdown
|
||||||
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
|
|
||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
ENTRYPOINT []
|
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/init.d/ /etc/init.d/
|
||||||
COPY --from=alpine /etc/shutdown.d/ /etc/shutdown.d/
|
COPY --from=alpine /etc/shutdown.d/ /etc/shutdown.d/
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
IMAGE=runc
|
IMAGE=runc
|
||||||
NETWORK=1
|
NETWORK=1
|
||||||
DEPS=$(wildcard cmd/onboot/*.go)
|
|
||||||
|
|
||||||
include ../package.mk
|
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)
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user