Add support for Overlay volumes into the container.

Overlay mounts allow buildah bud and buildah from to
specify a directory on the disk that will be mounted
as an overlay into the container, where the overlay can be written to
but when the RUN or buildah run exits, the modified files will dissapear.

The basic idea is to be able to mount cache from the disk for things like yum/dnf/apt
to be able to be used and modified in the contianer on a run command, but to be
kept fresh for each RUN.

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>

Closes: #1560
Approved by: giuseppe
This commit is contained in:
Daniel J Walsh 2019-04-29 09:41:18 -04:00 committed by Atomic Bot
parent 3a30a6f8d8
commit bcc5e51a94
9 changed files with 170 additions and 20 deletions

View File

@ -191,6 +191,8 @@ type Builder struct {
TopLayer string
// Format for the build Image
Format string
// TempVolumes are temporary mount points created during container runs
TempVolumes map[string]bool
}
// BuilderInfo are used as objects to display container information

View File

@ -1071,7 +1071,7 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
}
}
// Skip anything that isn't a bind or tmpfs mount.
if m.Type != "bind" && m.Type != "tmpfs" {
if m.Type != "bind" && m.Type != "tmpfs" && m.Type != "overlay" {
logrus.Debugf("skipping mount of type %q on %q", m.Type, m.Destination)
continue
}
@ -1083,10 +1083,12 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
if err != nil {
return undoBinds, errors.Wrapf(err, "error examining %q for mounting in mount namespace", m.Source)
}
case "overlay":
fallthrough
case "tmpfs":
srcinfo, err = os.Stat("/")
if err != nil {
return undoBinds, errors.Wrapf(err, "error examining / to use as a template for a tmpfs")
return undoBinds, errors.Wrapf(err, "error examining / to use as a template for a %s", m.Type)
}
}
target := filepath.Join(spec.Root.Path, m.Destination)
@ -1145,6 +1147,12 @@ func setupChrootBindMounts(spec *specs.Spec, bundlePath string) (undoBinds func(
return undoBinds, errors.Wrapf(err, "error mounting tmpfs to %q in mount namespace (%q, %q)", m.Destination, target, strings.Join(m.Options, ","))
}
logrus.Debugf("mounted a tmpfs to %q", target)
case "overlay":
// Mount a overlay.
if err := mount.Mount(m.Source, target, m.Type, strings.Join(append(m.Options, "private"), ",")); err != nil {
return undoBinds, errors.Wrapf(err, "error mounting overlay to %q in mount namespace (%q, %q)", m.Destination, target, strings.Join(m.Options, ","))
}
logrus.Debugf("mounted a overlay to %q", target)
}
if err = unix.Statfs(target, &fs); err != nil {
return undoBinds, errors.Wrapf(err, "error checking if directory %q was bound read-only", target)

View File

@ -531,7 +531,7 @@ process.
container. The `OPTIONS` are a comma delimited list and can be:
* [rw|ro]
* [z|Z]
* [z|Z|O]
* [`[r]shared`|`[r]slave`|`[r]private`]
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
@ -547,6 +547,8 @@ You can add the `:ro` or `:rw` suffix to a volume to mount it read-only or
read-write mode, respectively. By default, the volumes are mounted read-write.
See examples.
`Labeling Volume Mounts`
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
@ -560,6 +562,21 @@ content label. Shared volume labels allow all containers to read/write content.
The `Z` option tells Buildah to label the content with a private unshared label.
Only the current container can use a private volume.
`Overlay Volume Mounts`
The `:O` flag tells Buildah to mount the directory from the host as a temporary storage using the Overlay file system. The `RUN` command containers are allowed to modify contents within the mountpoint and are stored in the container storage in a separate directory. In Ovelay FS terms the source directory will be the lower, and the container storage directory will be the upper. Modifications to the mount point are destroyed when the `RUN` command finishes executing, similar to a tmpfs mount point.
Any subsequent execution of `RUN` commands sees the original source directory content, any changes from previous RUN commands no longer exists.
One use case of the `overlay` mount is sharing the package cache from the host into the container to allow speeding up builds.
Note:
- Overlay mounts are not currently supported in rootless mode.
- The `O` flag is not allowed to be specified with the `Z` or `z` flags. Content mounted into the container is labeled with the private label.
On SELinux systems, labels in the source directory needs to be readable by the container label. If not, SELinux container separation must be disabled for the container to work.
- Modification of the directory volume mounted into the container with an overlay mount can cause unexpected failures. It is recommended that you do not modify the directory until the container finishes running.
By default bind mounted volumes are `private`. That means any mounts done
inside container will not be visible on the host and vice versa. This behavior can
be changed by specifying a volume mount propagation property.
@ -621,6 +638,8 @@ buildah bud --security-opt label=level:s0:c100,c200 --cgroup-parent /path/to/cgr
buildah bud --volume /home/test:/myvol:ro,Z -t imageName .
buildah bud -v /var/lib/dnf:/var/lib/dnf:O -t imageName .
buildah bud --layers -t imageName .
buildah bud --no-cache -t imageName .

View File

@ -174,7 +174,7 @@ value can be entered. The password is entered without echo.
**--http-proxy**
By default proxy environment variables are passed into the container if set
for the buildah process. This can be disabled by setting the `--http-proxy`
for the Buildah process. This can be disabled by setting the `--http-proxy`
option to `false`. The environment variables passed in include `http_proxy`,
`https_proxy`, `ftp_proxy`, `no_proxy`, and also the upper case versions of
those.
@ -187,7 +187,7 @@ Sets the configuration for IPC namespaces when the container is subsequently
used for `buildah run`.
The configured value can be "" (the empty string) or "container" to indicate
that a new IPC namespace should be created, or it can be "host" to indicate
that the IPC namespace in which `buildah` itself is being run should be reused,
that the IPC namespace in which `Buildah` itself is being run should be reused,
or it can be the path to an IPC namespace which is already in use by
another process.
@ -237,7 +237,7 @@ Sets the configuration for network namespaces when the container is subsequently
used for `buildah run`.
The configured value can be "" (the empty string) or "container" to indicate
that a new network namespace should be created, or it can be "host" to indicate
that the network namespace in which `buildah` itself is being run should be
that the network namespace in which `Buildah` itself is being run should be
reused, or it can be the path to a network namespace which is already in use by
another process.
@ -247,7 +247,7 @@ Sets the configuration for PID namespaces when the container is subsequently
used for `buildah run`.
The configured value can be "" (the empty string) or "container" to indicate
that a new PID namespace should be created, or it can be "host" to indicate
that the PID namespace in which `buildah` itself is being run should be reused,
that the PID namespace in which `Buildah` itself is being run should be reused,
or it can be the path to a PID namespace which is already in use by another
process.
@ -329,7 +329,7 @@ Sets the configuration for user namespaces when the container is subsequently
used for `buildah run`.
The configured value can be "" (the empty string) or "container" to indicate
that a new user namespace should be created, it can be "host" to indicate that
the user namespace in which `buildah` itself is being run should be reused, or
the user namespace in which `Buildah` itself is being run should be reused, or
it can be the path to an user namespace which is already in use by another
process.
@ -383,7 +383,7 @@ filesytem level, on the container's contents, can be found in entries in the
Commands run using `buildah run` will default to being run in their own user
namespaces, configured using the UID and GID maps.
If --userns-gid-map-group is specified, but --userns-uid-map-user is not
specified, `buildah` will assume that the specified group name is also a
specified, `Buildah` will assume that the specified group name is also a
suitable user name to use as the default setting for this option.
**--userns-gid-map-group** *group*
@ -394,7 +394,7 @@ filesytem level, on the container's contents, can be found in entries in the
Commands run using `buildah run` will default to being run in their own user
namespaces, configured using the UID and GID maps.
If --userns-uid-map-user is specified, but --userns-gid-map-group is not
specified, `buildah` will assume that the specified user name is also a
specified, `Buildah` will assume that the specified user name is also a
suitable group name to use as the default setting for this option.
**--uts** *how*
@ -403,7 +403,7 @@ Sets the configuration for UTS namespaces when the container is subsequently
used for `buildah run`.
The configured value can be "" (the empty string) or "container" to indicate
that a new UTS namespace should be created, or it can be "host" to indicate
that the UTS namespace in which `buildah` itself is being run should be reused,
that the UTS namespace in which `Buildah` itself is being run should be reused,
or it can be the path to a UTS namespace which is already in use by another
process.
@ -414,7 +414,7 @@ process.
container. The `OPTIONS` are a comma delimited list and can be:
* [rw|ro]
* [z|Z]
* [z|Z|O]
* [`[r]shared`|`[r]slave`|`[r]private`|`[r]unbindable`]
The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR`
@ -430,6 +430,8 @@ You can add the `:ro` or `:rw` suffix to a volume to mount it read-only or
read-write mode, respectively. By default, the volumes are mounted read-write.
See examples.
`Labeling Volume Mounts`
Labeling systems like SELinux require that proper labels are placed on volume
content mounted into a container. Without a label, the security system might
prevent the processes running inside the container from using the content. By
@ -443,6 +445,21 @@ content label. Shared volume labels allow all containers to read/write content.
The `Z` option tells Buildah to label the content with a private unshared label.
Only the current container can use a private volume.
`Overlay Volume Mounts`
The `:O` flag tells Buildah to mount the directory from the host as a temporary storage using the Overlay file system. The `RUN` command containers are allowed to modify contents within the mountpoint and are stored in the container storage in a separate directory. In Ovelay FS terms the source directory will be the lower, and the container storage directory will be the upper. Modifications to the mount point are destroyed when the `RUN` command finishes executing, similar to a tmpfs mount point.
Any subsequent execution of `RUN` commands sees the original source directory content, any changes from previous RUN commands no longer exists.
One use case of the `overlay` mount is sharing the package cache from the host into the container to allow speeding up builds.
Note:
- Overlay mounts are not currently supported in rootless mode.
- The `O` flag is not allowed to be specified with the `Z` or `z` flags. Content mounted into the container is labeled with the private label.
On SELinux systems, labels in the source directory needs to be readable by the container label. If not, SELinux container separation must be disabled for the container to work.
- Modification of the directory volume mounted into the container with an overlay mount can cause unexpected failures. It is recommended that you do not modify the directory until the container finishes running.
By default bind mounted volumes are `private`. That means any mounts done
inside container will not be visible on the host and vice versa. This behavior can
be changed by specifying a volume mount propagation property.
@ -502,6 +519,8 @@ buildah from --ulimit nofile=1024:1028 --cgroup-parent /path/to/cgroup/parent my
buildah from --volume /home/test:/myvol:ro,Z myregistry/myrepository/imagename:imagetag
buildah from -v /var/lib/yum:/var/lib/yum:O myregistry/myrepository/imagename:imagetag
## Files
**registries.conf** (`/etc/containers/registries.conf`)

1
new.go
View File

@ -351,6 +351,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions
TopLayer: topLayer,
Args: options.Args,
Format: options.Format,
TempVolumes: map[string]bool{},
}
if options.Mount {

46
pkg/overlay/overlay.go Normal file
View File

@ -0,0 +1,46 @@
package overlay
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
// MountTemp creates a subdir of the contentDir based on the source directory
// from the source system. It then mounds up the source directory on to the
// generated mount point and returns the mount point to the caller.
func MountTemp(store storage.Store, containerId, source, dest string, rootUID, rootGID int) (specs.Mount, string, error) {
mount := specs.Mount{}
contentDir, err := store.ContainerDirectory(containerId)
if err != nil {
return mount, "", err
}
upperDir := filepath.Join(contentDir, "upper")
workDir := filepath.Join(contentDir, "work")
if err := idtools.MkdirAllAs(upperDir, 0700, rootUID, rootGID); err != nil {
return mount, "", errors.Wrapf(err, "failed to create the overlay %s directory", upperDir)
}
if err := idtools.MkdirAllAs(workDir, 0700, rootUID, rootGID); err != nil {
return mount, "", errors.Wrapf(err, "failed to create the overlay %s directory", workDir)
}
mount.Source = "overlay"
mount.Destination = dest
mount.Type = "overlay"
mount.Options = strings.Split(fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s,private", source, upperDir, workDir), ",")
return mount, contentDir, nil
}
// RemoveTemp removes temporary mountpoint and all content from its parent
// directory
func RemoveTemp(contentDir string) error {
return os.RemoveAll(contentDir)
}

View File

@ -14,6 +14,7 @@ import (
"unicode"
"github.com/containers/buildah"
"github.com/containers/buildah/pkg/unshare"
"github.com/containers/image/types"
"github.com/containers/storage/pkg/idtools"
"github.com/docker/go-units"
@ -216,9 +217,12 @@ func ValidateVolumeOpts(option string) error {
return errors.Errorf("invalid options %q, can only specify 1 'rw' or 'ro' option", option)
}
foundRWRO++
case "z", "Z":
case "z", "Z", "O":
if opt == "O" && unshare.IsRootless() {
return errors.Errorf("invalid options %q, overlay mounts not supported in rootless mode", option)
}
if foundLabelChange > 1 {
return errors.Errorf("invalid options %q, can only specify 1 'z' or 'Z' option", option)
return errors.Errorf("invalid options %q, can only specify 1 'z', 'Z', or 'O' option", option)
}
foundLabelChange++
case "private", "rprivate", "shared", "rshared", "slave", "rslave", "unbindable", "runbindable":

View File

@ -23,6 +23,7 @@ import (
"github.com/containernetworking/cni/libcni"
"github.com/containers/buildah/bind"
"github.com/containers/buildah/chroot"
"github.com/containers/buildah/pkg/overlay"
"github.com/containers/buildah/pkg/secrets"
"github.com/containers/buildah/pkg/unshare"
"github.com/containers/buildah/util"
@ -184,6 +185,7 @@ func (b *Builder) Run(command []string, options RunOptions) error {
if err != nil {
return errors.Wrapf(err, "error resolving mountpoints for container %q", b.ContainerID)
}
defer b.cleanupTempVolumes()
if options.CNIConfigDir == "" {
options.CNIConfigDir = b.CNIConfigDir
@ -438,7 +440,7 @@ func (b *Builder) setupMounts(mountPoint string, spec *specs.Spec, bundlePath st
}
// Get the list of explicitly-specified volume mounts.
volumes, err := runSetupVolumeMounts(spec.Linux.MountLabel, volumeMounts, optionMounts)
volumes, err := b.runSetupVolumeMounts(spec.Linux.MountLabel, volumeMounts, optionMounts, int(rootUID), int(rootGID))
if err != nil {
return err
}
@ -1537,11 +1539,21 @@ func addRlimits(ulimit []string, g *generate.Generator) error {
return nil
}
func runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount) ([]specs.Mount, error) {
var mounts []specs.Mount
func (b *Builder) cleanupTempVolumes() {
for tempVolume, val := range b.TempVolumes {
if val {
if err := overlay.RemoveTemp(tempVolume); err != nil {
logrus.Errorf(err.Error())
}
b.TempVolumes[tempVolume] = false
}
}
}
func (b *Builder) runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts []specs.Mount, rootUID, rootGID int) (mounts []specs.Mount, Err error) {
parseMount := func(host, container string, options []string) (specs.Mount, error) {
var foundrw, foundro, foundz, foundZ bool
var foundrw, foundro, foundz, foundZ, foundO bool
var rootProp string
for _, opt := range options {
switch opt {
@ -1553,6 +1565,8 @@ func runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts
foundz = true
case "Z":
foundZ = true
case "O":
foundO = true
case "private", "rprivate", "slave", "rslave", "shared", "rshared":
rootProp = opt
}
@ -1570,6 +1584,14 @@ func runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts
return specs.Mount{}, errors.Wrapf(err, "relabeling %q failed", host)
}
}
if foundO {
overlayMount, contentDir, err := overlay.MountTemp(b.store, b.ContainerID, host, container, rootUID, rootGID)
if err == nil {
b.TempVolumes[contentDir] = true
}
return overlayMount, err
}
if rootProp == "" {
options = append(options, "private")
}
@ -1577,13 +1599,14 @@ func runSetupVolumeMounts(mountLabel string, volumeMounts []string, optionMounts
Destination: container,
Type: "bind",
Source: host,
Options: options,
Options: append(options, "rbind"),
}, nil
}
// Bind mount volumes specified for this particular Run() invocation
for _, i := range optionMounts {
logrus.Debugf("setting up mounted volume at %q", i.Destination)
mount, err := parseMount(i.Source, i.Destination, append(i.Options, "rbind"))
mount, err := parseMount(i.Source, i.Destination, i.Options)
if err != nil {
return nil, err
}

28
tests/overlay.bats Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env bats
load helpers
@test "overlay specific level" {
if test -o "$BUILDAH_ISOLATION" = "rootless" ; then
skip "BUILDAH_ISOLATION = $BUILDAH_ISOLATION"
fi
image=alpine
mkdir ${TESTDIR}/lower
touch ${TESTDIR}/lower/foo
cid=$(buildah --debug=false from -v ${TESTDIR}/lower:/lower:O --quiet --signature-policy ${TESTSDIR}/policy.json $image)
# This should succeed
run_buildah --debug=false run $cid ls /lower/foo
# Create and remove content in the overlay directory, should succeed
run_buildah --debug=false run $cid touch /lower/bar
run_buildah --debug=false run $cid rm /lower/foo
# This should fail, second runs of containers go back to original
run_buildah 1 --debug=false run $cid ls /lower/bar
# This should fail
run ls ${TESTDIR}/lower/bar
[ "$status" -ne 0 ]
}