mirror of
https://github.com/moby/buildkit.git
synced 2026-08-09 17:18:11 +00:00
e.g. with busybox image:
OCI runtime create failed: container_linux.go:348:
starting container process caused "process_linux.go:402:
container init caused \"rootfs_linux.go:58:
mounting \\\"proc\\\" to rootfs \\\"/.../rootfs\\\" at \\\"/proc\\\"
caused \\\"mkdir /.../rootfs/proc: read-only file system\\\"\"": unknown
This is because we were setting the underlying snapshot readonly so the various
mountpoints (here /proc) cannot be created. This would not be necessary if
those mountpoints were present in images but they typically are not.
The right way to get around this (used e.g. by `ctr`) is to use a writeable
snapshot but to set root readonly in the OCI spec. In this configuration the
rootfs is writeable when mounts are processed but is then made readonly by the
runtime (runc) just before entering the user specified binary within the
container.
This involved a surprising amount of plumbing.
Use this new found ability in the dockerfile converter's `dispatchCopy`
function.
Signed-off-by: Ian Campbell <ijc@docker.com>
174 lines
3.9 KiB
Go
174 lines
3.9 KiB
Go
package runcexecutor
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
|
|
"github.com/containerd/containerd/contrib/seccomp"
|
|
"github.com/containerd/containerd/mount"
|
|
containerdoci "github.com/containerd/containerd/oci"
|
|
"github.com/containerd/continuity/fs"
|
|
runc "github.com/containerd/go-runc"
|
|
"github.com/moby/buildkit/cache"
|
|
"github.com/moby/buildkit/executor"
|
|
"github.com/moby/buildkit/executor/oci"
|
|
"github.com/moby/buildkit/identity"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type runcExecutor struct {
|
|
runc *runc.Runc
|
|
root string
|
|
}
|
|
|
|
func New(root string) (executor.Executor, error) {
|
|
if err := exec.Command("runc", "--version").Run(); err != nil {
|
|
return nil, errors.Wrap(err, "failed to find runc binary")
|
|
}
|
|
|
|
if err := os.MkdirAll(root, 0700); err != nil {
|
|
return nil, errors.Wrapf(err, "failed to create %s", root)
|
|
}
|
|
|
|
root, err := filepath.Abs(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// TODO: check that root is not symlink to fail early
|
|
|
|
runtime := &runc.Runc{
|
|
Log: filepath.Join(root, "runc-log.json"),
|
|
LogFormat: runc.JSON,
|
|
PdeathSignal: syscall.SIGKILL,
|
|
Setpgid: true,
|
|
}
|
|
|
|
w := &runcExecutor{
|
|
runc: runtime,
|
|
root: root,
|
|
}
|
|
return w, nil
|
|
}
|
|
|
|
func (w *runcExecutor) Exec(ctx context.Context, meta executor.Meta, root cache.Mountable, mounts []executor.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
|
|
|
|
resolvConf, err := oci.GetResolvConf(ctx, w.root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hostsFile, err := oci.GetHostsFile(ctx, w.root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rootMount, err := root.Mount(ctx, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
id := identity.NewID()
|
|
bundle := filepath.Join(w.root, id)
|
|
|
|
if err := os.Mkdir(bundle, 0700); err != nil {
|
|
return err
|
|
}
|
|
defer os.RemoveAll(bundle)
|
|
rootFSPath := filepath.Join(bundle, "rootfs")
|
|
if err := os.Mkdir(rootFSPath, 0700); err != nil {
|
|
return err
|
|
}
|
|
if err := mount.All(rootMount, rootFSPath); err != nil {
|
|
return err
|
|
}
|
|
defer mount.Unmount(rootFSPath, 0)
|
|
|
|
uid, gid, err := oci.GetUser(ctx, rootFSPath, meta.User)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := os.Create(filepath.Join(bundle, "config.json"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
opts := []containerdoci.SpecOpts{containerdoci.WithUIDGID(uid, gid), seccomp.WithDefaultProfile()}
|
|
if meta.ReadonlyRootFS {
|
|
opts = append(opts, containerdoci.WithRootFSReadonly())
|
|
}
|
|
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, opts...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer cleanup()
|
|
|
|
spec.Root.Path = rootFSPath
|
|
if _, ok := root.(cache.ImmutableRef); ok { // TODO: pass in with mount, not ref type
|
|
spec.Root.Readonly = true
|
|
}
|
|
|
|
newp, err := fs.RootPath(rootFSPath, meta.Cwd)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "working dir %s points to invalid target", newp)
|
|
}
|
|
if err := os.MkdirAll(newp, 0700); err != nil {
|
|
return errors.Wrapf(err, "failed to create working directory %s", newp)
|
|
}
|
|
|
|
if err := json.NewEncoder(f).Encode(spec); err != nil {
|
|
return err
|
|
}
|
|
|
|
logrus.Debugf("> running %s %v", id, meta.Args)
|
|
|
|
status, err := w.runc.Run(ctx, id, bundle, &runc.CreateOpts{
|
|
IO: &forwardIO{stdin: stdin, stdout: stdout, stderr: stderr},
|
|
})
|
|
logrus.Debugf("< completed %s %v %v", id, status, err)
|
|
if status != 0 {
|
|
select {
|
|
case <-ctx.Done():
|
|
// runc can't report context.Cancelled directly
|
|
return errors.Wrapf(ctx.Err(), "exit code %d", status)
|
|
default:
|
|
}
|
|
return errors.Errorf("exit code %d", status)
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
type forwardIO struct {
|
|
stdin io.ReadCloser
|
|
stdout, stderr io.WriteCloser
|
|
}
|
|
|
|
func (s *forwardIO) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (s *forwardIO) Set(cmd *exec.Cmd) {
|
|
cmd.Stdin = s.stdin
|
|
cmd.Stdout = s.stdout
|
|
cmd.Stderr = s.stderr
|
|
}
|
|
|
|
func (s *forwardIO) Stdin() io.WriteCloser {
|
|
return nil
|
|
}
|
|
|
|
func (s *forwardIO) Stdout() io.ReadCloser {
|
|
return nil
|
|
}
|
|
|
|
func (s *forwardIO) Stderr() io.ReadCloser {
|
|
return nil
|
|
}
|