mirror of
https://github.com/moby/buildkit.git
synced 2026-07-03 13:17:55 +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>
144 lines
3.1 KiB
Go
144 lines
3.1 KiB
Go
package containerdexecutor
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd"
|
|
"github.com/containerd/containerd/cio"
|
|
containerdoci "github.com/containerd/containerd/oci"
|
|
"github.com/moby/buildkit/cache"
|
|
"github.com/moby/buildkit/executor"
|
|
"github.com/moby/buildkit/executor/oci"
|
|
"github.com/moby/buildkit/identity"
|
|
"github.com/moby/buildkit/snapshot"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type containerdExecutor struct {
|
|
client *containerd.Client
|
|
root string
|
|
}
|
|
|
|
func New(client *containerd.Client, root string) executor.Executor {
|
|
return containerdExecutor{
|
|
client: client,
|
|
root: root,
|
|
}
|
|
}
|
|
|
|
func (w containerdExecutor) Exec(ctx context.Context, meta executor.Meta, root cache.Mountable, mounts []executor.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) (err error) {
|
|
id := identity.NewID()
|
|
|
|
resolvConf, err := oci.GetResolvConf(ctx, w.root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
hostsFile, err := oci.GetHostsFile(ctx, w.root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rootMounts, err := root.Mount(ctx, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
uid, gid, err := oci.ParseUser(meta.User)
|
|
if err != nil {
|
|
lm := snapshot.LocalMounter(rootMounts)
|
|
rootfsPath, err := lm.Mount()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
uid, gid, err = oci.GetUser(ctx, rootfsPath, meta.User)
|
|
if err != nil {
|
|
lm.Unmount()
|
|
return err
|
|
}
|
|
lm.Unmount()
|
|
}
|
|
|
|
opts := []containerdoci.SpecOpts{containerdoci.WithUIDGID(uid, gid)}
|
|
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()
|
|
|
|
container, err := w.client.NewContainer(ctx, id,
|
|
containerd.WithSpec(spec),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
if err1 := container.Delete(context.TODO()); err == nil && err1 != nil {
|
|
err = errors.Wrapf(err1, "failed to delete container %s", id)
|
|
}
|
|
}()
|
|
|
|
if stdin == nil {
|
|
stdin = &emptyReadCloser{}
|
|
}
|
|
|
|
task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStreams(stdin, stdout, stderr)), containerd.WithRootFS(rootMounts))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer func() {
|
|
if _, err1 := task.Delete(context.TODO()); err == nil && err1 != nil {
|
|
err = errors.Wrapf(err1, "failed to delete task %s", id)
|
|
}
|
|
}()
|
|
|
|
// TODO: Configure bridge networking
|
|
|
|
if err := task.Start(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
statusCh, err := task.Wait(context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var cancel func()
|
|
ctxDone := ctx.Done()
|
|
for {
|
|
select {
|
|
case <-ctxDone:
|
|
ctxDone = nil
|
|
var killCtx context.Context
|
|
killCtx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
|
|
task.Kill(killCtx, syscall.SIGKILL)
|
|
case status := <-statusCh:
|
|
if cancel != nil {
|
|
cancel()
|
|
}
|
|
if status.ExitCode() != 0 {
|
|
return errors.Errorf("process returned non-zero exit code: %d", status.ExitCode())
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
type emptyReadCloser struct{}
|
|
|
|
func (*emptyReadCloser) Read([]byte) (int, error) {
|
|
return 0, io.EOF
|
|
}
|
|
|
|
func (*emptyReadCloser) Close() error {
|
|
return nil
|
|
}
|