Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

volumes: Implement subpath mount #45687

Merged
merged 12 commits into from
Jan 19, 2024
8 changes: 8 additions & 0 deletions api/server/router/container/container_routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,14 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo
}
}

if versions.LessThan(version, "1.45") {
for _, m := range hostConfig.Mounts {
if m.VolumeOptions != nil && m.VolumeOptions.Subpath != "" {
return errdefs.InvalidParameter(errors.New("VolumeOptions.Subpath needs API v1.45 or newer"))
}
}
}

var warnings []string
if warn, err := handleMACAddressBC(config, hostConfig, networkingConfig, version); err != nil {
return err
Expand Down
4 changes: 4 additions & 0 deletions api/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,10 @@ definitions:
type: "object"
additionalProperties:
type: "string"
Subpath:
description: "Source path inside the volume. Must be relative without any back traversals."
type: "string"
example: "dir-inside-volume/subdirectory"
TmpfsOptions:
description: "Optional configuration for the `tmpfs` type."
type: "object"
Expand Down
1 change: 1 addition & 0 deletions api/types/mount/mount.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type BindOptions struct {
type VolumeOptions struct {
NoCopy bool `json:",omitempty"`
Labels map[string]string `json:",omitempty"`
Subpath string `json:",omitempty"`
DriverConfig *Driver `json:",omitempty"`
}

Expand Down
4 changes: 2 additions & 2 deletions container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,14 +514,14 @@ func (container *Container) AddMountPointWithVolume(destination string, vol volu
}

// UnmountVolumes unmounts all volumes
func (container *Container) UnmountVolumes(volumeEventLog func(name string, action events.Action, attributes map[string]string)) error {
func (container *Container) UnmountVolumes(ctx context.Context, volumeEventLog func(name string, action events.Action, attributes map[string]string)) error {
var errs []string
for _, volumeMount := range container.MountPoints {
if volumeMount.Volume == nil {
continue
}

if err := volumeMount.Cleanup(); err != nil {
if err := volumeMount.Cleanup(ctx); err != nil {
errs = append(errs, err.Error())
continue
}
Expand Down
39 changes: 9 additions & 30 deletions container/container_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import (
"github.com/docker/docker/api/types/events"
mounttypes "github.com/docker/docker/api/types/mount"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/volume"
volumemounts "github.com/docker/docker/volume/mounts"
"github.com/moby/sys/mount"
"github.com/opencontainers/selinux/go-selinux/label"
Expand Down Expand Up @@ -129,34 +127,11 @@ func (container *Container) NetworkMounts() []Mount {
}

// CopyImagePathContent copies files in destination to the volume.
func (container *Container) CopyImagePathContent(v volume.Volume, destination string) error {
rootfs, err := container.GetResourcePath(destination)
if err != nil {
return err
}

if _, err := os.Stat(rootfs); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}

id := stringid.GenerateRandomID()
path, err := v.Mount(id)
if err != nil {
return err
}

defer func() {
if err := v.Unmount(id); err != nil {
log.G(context.TODO()).Warnf("error while unmounting volume %s: %v", v.Name(), err)
}
}()
if err := label.Relabel(path, container.MountLabel, true); err != nil && !errors.Is(err, syscall.ENOTSUP) {
func (container *Container) CopyImagePathContent(volumePath, destination string) error {
if err := label.Relabel(volumePath, container.MountLabel, true); err != nil && !errors.Is(err, syscall.ENOTSUP) {
return err
}
return copyExistingContents(rootfs, path)
return copyExistingContents(destination, volumePath)
}

// ShmResourcePath returns path to shm
Expand Down Expand Up @@ -396,7 +371,7 @@ func (container *Container) DetachAndUnmount(volumeEventLog func(name string, ac
Warn("Unable to unmount")
}
}
return container.UnmountVolumes(volumeEventLog)
return container.UnmountVolumes(ctx, volumeEventLog)
}

// ignoreUnsupportedXAttrs ignores errors when extended attributes
Expand All @@ -419,9 +394,13 @@ func copyExistingContents(source, destination string) error {
return err
}
if len(dstList) != 0 {
// destination is not empty, do not copy
log.G(context.TODO()).WithFields(log.Fields{
"source": source,
"destination": destination,
}).Debug("destination is not empty, do not copy")
return nil
}

return fs.CopyDir(destination, source, ignoreUnsupportedXAttrs())
}

Expand Down
3 changes: 2 additions & 1 deletion container/container_windows.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package container // import "github.com/docker/docker/container"

import (
"context"
"fmt"
"os"
"path/filepath"
Expand Down Expand Up @@ -128,7 +129,7 @@ func (container *Container) ConfigMounts() []Mount {
// On Windows it only delegates to `UnmountVolumes` since there is nothing to
// force unmount.
func (container *Container) DetachAndUnmount(volumeEventLog func(name string, action events.Action, attributes map[string]string)) error {
return container.UnmountVolumes(volumeEventLog)
return container.UnmountVolumes(context.TODO(), volumeEventLog)
}

// TmpfsMounts returns the list of tmpfs mounts
Expand Down
39 changes: 39 additions & 0 deletions daemon/container_operations_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,45 @@ func (daemon *Daemon) getPIDContainer(id string) (*container.Container, error) {
return ctr, nil
}

// setupContainerDirs sets up base container directories (root, ipc, tmpfs and secrets).
func (daemon *Daemon) setupContainerDirs(c *container.Container) (_ []container.Mount, err error) {
if err := daemon.setupContainerMountsRoot(c); err != nil {
return nil, err
}

if err := daemon.setupIPCDirs(c); err != nil {
return nil, err
}

if err := daemon.setupSecretDir(c); err != nil {
return nil, err
}
defer func() {
if err != nil {
daemon.cleanupSecretDir(c)
}
}()

var ms []container.Mount
if !c.HostConfig.IpcMode.IsPrivate() && !c.HostConfig.IpcMode.IsEmpty() {
ms = append(ms, c.IpcMounts()...)
}

tmpfsMounts, err := c.TmpfsMounts()
if err != nil {
return nil, err
}
ms = append(ms, tmpfsMounts...)

secretMounts, err := c.SecretMounts()
if err != nil {
return nil, err
}
ms = append(ms, secretMounts...)

return ms, nil
}

func (daemon *Daemon) setupIPCDirs(c *container.Container) error {
ipcMode := c.HostConfig.IpcMode

Expand Down
11 changes: 8 additions & 3 deletions daemon/containerfs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (

"github.com/docker/docker/api/types"
"github.com/docker/docker/container"
"github.com/docker/docker/internal/compatcontext"
"github.com/docker/docker/internal/mounttree"
"github.com/docker/docker/internal/unshare"
"github.com/docker/docker/pkg/fileutils"
Expand Down Expand Up @@ -54,6 +55,8 @@ type containerFSView struct {

// openContainerFS opens a new view of the container's filesystem.
func (daemon *Daemon) openContainerFS(container *container.Container) (_ *containerFSView, err error) {
ctx := context.TODO()

if err := daemon.Mount(container); err != nil {
return nil, err
}
Expand All @@ -63,13 +66,15 @@ func (daemon *Daemon) openContainerFS(container *container.Container) (_ *contai
}
}()

mounts, err := daemon.setupMounts(container)
mounts, cleanup, err := daemon.setupMounts(ctx, container)
if err != nil {
return nil, err
}
defer func() {
ctx := compatcontext.WithoutCancel(ctx)
cleanup(ctx)
if err != nil {
_ = container.UnmountVolumes(daemon.LogVolumeEvent)
_ = container.UnmountVolumes(ctx, daemon.LogVolumeEvent)
}
}()

Expand Down Expand Up @@ -207,7 +212,7 @@ func (vw *containerFSView) Close() error {
runtime.SetFinalizer(vw, nil)
close(vw.todo)
err := multierror.Append(nil, <-vw.done)
err = multierror.Append(err, vw.ctr.UnmountVolumes(vw.d.LogVolumeEvent))
err = multierror.Append(err, vw.ctr.UnmountVolumes(context.TODO(), vw.d.LogVolumeEvent))
err = multierror.Append(err, vw.d.Unmount(vw.ctr))
return err.ErrorOrNil()
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func (daemon *Daemon) create(ctx context.Context, daemonCfg *config.Config, opts
return nil, err
}

if err := daemon.createContainerOSSpecificSettings(ctr, opts.params.Config, opts.params.HostConfig); err != nil {
if err := daemon.createContainerOSSpecificSettings(ctx, ctr, opts.params.Config, opts.params.HostConfig); err != nil {
return nil, err
}

Expand Down
47 changes: 41 additions & 6 deletions daemon/create_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ import (
containertypes "github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/container"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/internal/compatcontext"
"github.com/docker/docker/oci"
volumemounts "github.com/docker/docker/volume/mounts"
volumeopts "github.com/docker/docker/volume/service/opts"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
)

// createContainerOSSpecificSettings performs host-OS specific container create functionality
func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
func (daemon *Daemon) createContainerOSSpecificSettings(ctx context.Context, container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
if err := daemon.Mount(container); err != nil {
return err
}
Expand All @@ -45,7 +49,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con
// Skip volumes for which we already have something mounted on that
// destination because of a --volume-from.
if container.HasMountFor(destination) {
log.G(context.TODO()).WithField("container", container.ID).WithField("destination", spec).Debug("mountpoint already exists, skipping anonymous volume")
log.G(ctx).WithField("container", container.ID).WithField("destination", spec).Debug("mountpoint already exists, skipping anonymous volume")
// Not an error, this could easily have come from the image config.
continue
}
Expand All @@ -70,12 +74,12 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con

container.AddMountPointWithVolume(destination, &volumeWrapper{v: v, s: daemon.volumes}, true)
}
return daemon.populateVolumes(container)
return daemon.populateVolumes(ctx, container)
}

// populateVolumes copies data from the container's rootfs into the volume for non-binds.
// this is only called when the container is created.
func (daemon *Daemon) populateVolumes(c *container.Container) error {
func (daemon *Daemon) populateVolumes(ctx context.Context, c *container.Container) error {
for _, mnt := range c.MountPoints {
if mnt.Volume == nil {
continue
Expand All @@ -85,10 +89,41 @@ func (daemon *Daemon) populateVolumes(c *container.Container) error {
continue
}

log.G(context.TODO()).Debugf("copying image data from %s:%s, to %s", c.ID, mnt.Destination, mnt.Name)
if err := c.CopyImagePathContent(mnt.Volume, mnt.Destination); err != nil {
if err := daemon.populateVolume(ctx, c, mnt); err != nil {
return err
}
}
return nil
}

func (daemon *Daemon) populateVolume(ctx context.Context, c *container.Container, mnt *volumemounts.MountPoint) error {
ctrDestPath, err := c.GetResourcePath(mnt.Destination)
if err != nil {
return err
}

if _, err := os.Stat(ctrDestPath); err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}

volumePath, cleanup, err := mnt.Setup(ctx, c.MountLabel, daemon.idMapping.RootPair(), nil)
if err != nil {
if errdefs.IsNotFound(err) {
return nil
}
log.G(ctx).WithError(err).Debugf("can't copy data from %s:%s, to %s", c.ID, mnt.Destination, volumePath)
return errors.Wrapf(err, "failed to populate volume")
}
defer mnt.Cleanup(compatcontext.WithoutCancel(ctx))
defer cleanup(compatcontext.WithoutCancel(ctx))

log.G(ctx).Debugf("copying image data from %s:%s, to %s", c.ID, mnt.Destination, volumePath)
if err := c.CopyImagePathContent(volumePath, ctrDestPath); err != nil {
return err
}

return nil
}
4 changes: 2 additions & 2 deletions daemon/create_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

// createContainerOSSpecificSettings performs host-OS specific container create functionality
func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
func (daemon *Daemon) createContainerOSSpecificSettings(ctx context.Context, container *container.Container, config *containertypes.Config, hostConfig *containertypes.HostConfig) error {
if containertypes.Isolation.IsDefault(hostConfig.Isolation) {
// Make sure the host config has the default daemon isolation if not specified by caller.
hostConfig.Isolation = daemon.defaultIsolation
Expand All @@ -34,7 +34,7 @@ func (daemon *Daemon) createContainerOSSpecificSettings(container *container.Con

// Create the volume in the volume driver. If it doesn't exist,
// a new one will be created.
v, err := daemon.volumes.Create(context.TODO(), "", volumeDriver, volumeopts.WithCreateReference(container.ID))
v, err := daemon.volumes.Create(ctx, "", volumeDriver, volumeopts.WithCreateReference(container.ID))
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ func (daemon *Daemon) restore(cfg *configStore) error {
ces.ExitCode = 255
}
c.SetStopped(&ces)
daemon.Cleanup(c)
daemon.Cleanup(context.TODO(), c)
if err := c.CheckpointTo(daemon.containersReplica); err != nil {
baseLogger.WithError(err).Error("failed to update stopped container state")
}
Expand Down
2 changes: 1 addition & 1 deletion daemon/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func (daemon *Daemon) handleContainerExit(c *container.Container, e *libcontaine
"exitCode": strconv.Itoa(exitStatus.ExitCode),
"execDuration": strconv.Itoa(int(execDuration.Seconds())),
}
daemon.Cleanup(c)
daemon.Cleanup(context.TODO(), c)

if restart {
c.RestartCount++
Expand Down