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:
parent
3a30a6f8d8
commit
bcc5e51a94
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 .
|
||||
|
|
|
|||
|
|
@ -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
1
new.go
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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":
|
||||
|
|
|
|||
35
run_linux.go
35
run_linux.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ]
|
||||
}
|
||||
Loading…
Reference in New Issue