mirror of
https://github.com/moby/buildkit.git
synced 2026-10-28 16:44:37 +08:00
The rootless spec conversion removes the /sys mount, so the runtime never creates its mount point in the rootfs. A rootful build gets that empty directory, which meant the same LLB left different mount points behind depending on whether the worker was rootless. Recreate the mount points of the removed mounts after the container has exited, rather than cleaning them up from the rootful result, which would change existing output and need a compatibility version bump. Creating them up front would instead turn a mount point that the image does not ship into a directory the build can write to. Nested destinations are skipped: the runtime creates those inside the parent mount, where they never reach the rootfs. Fixes #6686 Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
194 lines
5.4 KiB
Go
194 lines
5.4 KiB
Go
//go:build !windows
|
|
|
|
package containerdexecutor
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"slices"
|
|
|
|
ctd "github.com/containerd/containerd/v2/client"
|
|
"github.com/containerd/containerd/v2/core/mount"
|
|
containerdoci "github.com/containerd/containerd/v2/pkg/oci"
|
|
"github.com/containerd/continuity/fs"
|
|
"github.com/moby/buildkit/executor"
|
|
"github.com/moby/buildkit/executor/oci"
|
|
"github.com/moby/buildkit/snapshot"
|
|
"github.com/moby/buildkit/solver/pb"
|
|
"github.com/moby/buildkit/util/bklog"
|
|
"github.com/moby/buildkit/util/network"
|
|
rootlessspecconv "github.com/moby/buildkit/util/rootless/specconv"
|
|
"github.com/moby/sys/user"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func getUserSpec(user, rootfsPath string) (specs.User, error) {
|
|
var err error
|
|
var uid, gid uint32
|
|
var sgids []uint32
|
|
if rootfsPath != "" {
|
|
uid, gid, sgids, err = oci.GetUser(rootfsPath, user)
|
|
} else {
|
|
uid, gid, err = oci.ParseUIDGID(user)
|
|
}
|
|
if err != nil {
|
|
return specs.User{}, errors.WithStack(err)
|
|
}
|
|
return specs.User{
|
|
UID: uid,
|
|
GID: gid,
|
|
AdditionalGids: sgids,
|
|
}, nil
|
|
}
|
|
|
|
func (w *containerdExecutor) prepareExecutionEnv(ctx context.Context, rootMount executor.Mount, mounts []executor.Mount, meta executor.Meta, details *containerState, netMode pb.NetMode) (string, string, func(), error) {
|
|
var releasers []func()
|
|
releaseAll := func() {
|
|
for _, release := range slices.Backward(releasers) {
|
|
release()
|
|
}
|
|
}
|
|
|
|
stateDirRoot, err := os.OpenRoot(w.root)
|
|
if err != nil {
|
|
releaseAll()
|
|
return "", "", nil, err
|
|
}
|
|
defer stateDirRoot.Close()
|
|
|
|
resolvConfName, err := oci.GetResolvConf(ctx, stateDirRoot, nil, w.dnsConfig, netMode)
|
|
if err != nil {
|
|
releaseAll()
|
|
return "", "", nil, err
|
|
}
|
|
resolvConf := filepath.Join(w.root, resolvConfName)
|
|
|
|
hostsName, clean, err := oci.GetHostsFile(ctx, stateDirRoot, meta.ExtraHosts, nil, meta.Hostname)
|
|
if err != nil {
|
|
releaseAll()
|
|
return "", "", nil, err
|
|
}
|
|
hostsFile := filepath.Join(w.root, hostsName)
|
|
if clean != nil {
|
|
releasers = append(releasers, clean)
|
|
}
|
|
mountable, err := rootMount.Src.Mount(ctx, false)
|
|
if err != nil {
|
|
releaseAll()
|
|
return "", "", nil, err
|
|
}
|
|
|
|
rootMounts, release, err := mountable.Mount()
|
|
if err != nil {
|
|
releaseAll()
|
|
return "", "", nil, err
|
|
}
|
|
details.rootMounts = rootMounts
|
|
|
|
if release != nil {
|
|
releasers = append(releasers, func() {
|
|
if err := release(); err != nil {
|
|
bklog.G(ctx).WithError(err).Error("failed to release root mount")
|
|
}
|
|
})
|
|
}
|
|
lm := snapshot.LocalMounterWithMounts(rootMounts)
|
|
rootfsPath, err := lm.Mount()
|
|
if err != nil {
|
|
releaseAll()
|
|
return "", "", nil, err
|
|
}
|
|
details.rootfsPath = rootfsPath
|
|
releasers = append(releasers, func() {
|
|
if err := lm.Unmount(); err != nil {
|
|
bklog.G(ctx).WithError(err).Error("failed to unmount rootfs")
|
|
}
|
|
})
|
|
releasers = append(releasers, executor.MountStubsCleaner(ctx, details.rootfsPath, mounts, meta.RemoveMountStubsRecursive))
|
|
|
|
return resolvConf, hostsFile, releaseAll, nil
|
|
}
|
|
|
|
func (w *containerdExecutor) ensureCWD(details *containerState, meta executor.Meta) error {
|
|
newp, err := fs.RootPath(details.rootfsPath, meta.Cwd)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "working dir %s points to invalid target", newp)
|
|
}
|
|
|
|
uid, gid, _, err := oci.GetUser(details.rootfsPath, meta.User)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := os.Stat(newp); err != nil {
|
|
if err := user.MkdirAllAndChown(newp, 0755, int(uid), int(gid)); err != nil {
|
|
return errors.Wrapf(err, "failed to create working directory %s", newp)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *containerdExecutor) createOCISpec(ctx context.Context, id, resolvConf, hostsFile string, namespace network.Namespace, mounts []executor.Mount, meta executor.Meta, details *containerState) (*specs.Spec, func(), error) {
|
|
var releasers []func()
|
|
releaseAll := func() {
|
|
for _, release := range slices.Backward(releasers) {
|
|
release()
|
|
}
|
|
}
|
|
|
|
uid, gid, sgids, err := oci.GetUser(details.rootfsPath, meta.User)
|
|
if err != nil {
|
|
releaseAll()
|
|
return nil, nil, err
|
|
}
|
|
|
|
opts := []containerdoci.SpecOpts{oci.WithUIDGID(uid, gid, sgids)}
|
|
if meta.ReadonlyRootFS {
|
|
opts = append(opts, containerdoci.WithRootFSReadonly())
|
|
}
|
|
|
|
processMode := oci.ProcessSandbox // FIXME(AkihiroSuda)
|
|
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, processMode, nil, w.apparmorProfile, w.selinux, w.traceSocket, w.cdiManager, opts...)
|
|
if err != nil {
|
|
releaseAll()
|
|
return nil, nil, err
|
|
}
|
|
releasers = append(releasers, cleanup)
|
|
spec.Process.Terminal = meta.Tty
|
|
if w.rootless {
|
|
removedMounts, err := rootlessspecconv.ToRootless(spec)
|
|
if err != nil {
|
|
releaseAll()
|
|
return nil, nil, err
|
|
}
|
|
// The runtime no longer sets these mounts up, but a rootful build still gets
|
|
// their mount points left in the rootfs. The caller recreates them once the
|
|
// container is gone. moby/buildkit#6686
|
|
details.removedMounts = removedMounts
|
|
}
|
|
return spec, releaseAll, nil
|
|
}
|
|
|
|
func (d *containerState) getTaskOpts() ([]ctd.NewTaskOpts, error) {
|
|
rootfs := ctd.WithRootFS([]mount.Mount{{
|
|
Source: d.rootfsPath,
|
|
Type: "bind",
|
|
Options: []string{"rbind"},
|
|
}})
|
|
if runtime.GOOS == "freebsd" {
|
|
rootfs = ctd.WithRootFS([]mount.Mount{{
|
|
Source: d.rootfsPath,
|
|
Type: "nullfs",
|
|
Options: []string{},
|
|
}})
|
|
}
|
|
return []ctd.NewTaskOpts{rootfs}, nil
|
|
}
|
|
|
|
func setArgs(spec *specs.Process, args []string) {
|
|
spec.Args = args
|
|
}
|