buildkit: add support for mount=type=cache
Following PR inroduces a new mount type=cache in parity to buildkit which allows users to share persistant cache between different builds. Allowing users to cache content generated by bussiness logic or enhance build performance by caching components across builds. Signed-off-by: Aditya Rajan <arajan@redhat.com>
This commit is contained in:
parent
982717a6c2
commit
fc69aa68c2
|
@ -102,7 +102,7 @@ A Containerfile is similar to a Makefile.
|
|||
|
||||
Attach a filesystem mount to the container
|
||||
|
||||
Current supported mount TYPES are bind, and tmpfs.
|
||||
Current supported mount TYPES are bind, cache, secret and tmpfs.
|
||||
|
||||
e.g.
|
||||
|
||||
|
@ -110,6 +110,8 @@ Current supported mount TYPES are bind, and tmpfs.
|
|||
|
||||
mount=type=tmpfs,tmpfs-size=512M,destination=/path/in/container
|
||||
|
||||
mount=type=secret,id=mysecret cat /run/secrets/mysecret
|
||||
|
||||
Common Options:
|
||||
|
||||
· src, source: mount source spec for bind and volume. Mandatory for bind.
|
||||
|
@ -132,6 +134,19 @@ Current supported mount TYPES are bind, and tmpfs.
|
|||
|
||||
· tmpcopyup: Path that is shadowed by the tmpfs mount is recursively copied up to the tmpfs itself.
|
||||
|
||||
Options specific to cache:
|
||||
|
||||
· id: Create a separate cache directory for a particular id.
|
||||
|
||||
· mode: File mode for new cache directory in octal. Default 0755.
|
||||
|
||||
· ro, readonly: read only cache if set.
|
||||
|
||||
· uid: uid for cache directory.
|
||||
|
||||
· gid: gid for cache directory.
|
||||
|
||||
|
||||
**RUN Secrets**
|
||||
|
||||
The RUN command has a feature to allow the passing of secret information into the image build. These secrets files can be used during the RUN command but are not committed to the final image. The `RUN` command supports the `--mount` option to identify the secret file. A secret file from the host is mounted into the container while the image is being built.
|
||||
|
|
|
@ -103,7 +103,7 @@ BUILDAH\_ISOLATION environment variable. `export BUILDAH_ISOLATION=oci`
|
|||
|
||||
Attach a filesystem mount to the container
|
||||
|
||||
Current supported mount TYPES are bind, and tmpfs. <sup>[[1]](#Footnote1)</sup>
|
||||
Current supported mount TYPES are bind, cache, secret and tmpfs. <sup>[[1]](#Footnote1)</sup>
|
||||
|
||||
e.g.
|
||||
|
||||
|
@ -111,6 +111,8 @@ Current supported mount TYPES are bind, and tmpfs. <sup>[[1]](#Footnote1)</sup>
|
|||
|
||||
type=tmpfs,tmpfs-size=512M,destination=/path/in/container
|
||||
|
||||
type=cache,target=/path/in/container
|
||||
|
||||
Common Options:
|
||||
|
||||
· src, source: mount source spec for bind and volume. Mandatory for bind.
|
||||
|
@ -133,6 +135,22 @@ Current supported mount TYPES are bind, and tmpfs. <sup>[[1]](#Footnote1)</sup>
|
|||
|
||||
· tmpcopyup: Path that is shadowed by the tmpfs mount is recursively copied up to the tmpfs itself.
|
||||
|
||||
Options specific to secret:
|
||||
|
||||
· id: the identifier for the secret passed into the `buildah bud --secret` or `podman build --secret` command.
|
||||
|
||||
Options specific to cache:
|
||||
|
||||
· id: Create a separate cache directory for a particular id.
|
||||
|
||||
· mode: File mode for new cache directory in octal. Default 0755.
|
||||
|
||||
· ro, readonly: read only cache if set.
|
||||
|
||||
· uid: uid for cache directory.
|
||||
|
||||
· gid: gid for cache directory.
|
||||
|
||||
**--network**, **--net**=*mode*
|
||||
|
||||
Sets the configuration for the network namespace for the container.
|
||||
|
|
|
@ -37,6 +37,11 @@ const (
|
|||
TypeBind = "bind"
|
||||
// TypeTmpfs is the type for mounting tmpfs
|
||||
TypeTmpfs = "tmpfs"
|
||||
// TypeCache is the type for mounting a common persistent cache from host
|
||||
TypeCache = "cache"
|
||||
// mount=type=cache must create a persistent directory on host so its available for all consecutive builds.
|
||||
// Lifecycle of following directory will be inherited from how host machine treats temporary directory
|
||||
BuildahCacheDir = "buildah-cache"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -315,6 +320,15 @@ func getMounts(mounts []string, contextDir string) (map[string]specs.Mount, erro
|
|||
return nil, errors.Wrapf(errDuplicateDest, mount.Destination)
|
||||
}
|
||||
finalMounts[mount.Destination] = mount
|
||||
case TypeCache:
|
||||
mount, err := GetCacheMount(tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := finalMounts[mount.Destination]; ok {
|
||||
return nil, errors.Wrapf(errDuplicateDest, mount.Destination)
|
||||
}
|
||||
finalMounts[mount.Destination] = mount
|
||||
case TypeTmpfs:
|
||||
mount, err := GetTmpfsMount(tokens)
|
||||
if err != nil {
|
||||
|
@ -414,6 +428,125 @@ func GetBindMount(args []string, contextDir string) (specs.Mount, error) {
|
|||
return newMount, nil
|
||||
}
|
||||
|
||||
// GetCacheMount parses a single cache mount entry from the --mount flag.
|
||||
func GetCacheMount(args []string) (specs.Mount, error) {
|
||||
var err error
|
||||
var (
|
||||
setDest bool
|
||||
setShared bool
|
||||
setReadOnly bool
|
||||
)
|
||||
newMount := specs.Mount{
|
||||
Type: TypeBind,
|
||||
}
|
||||
// if id is set a new subdirectory with `id` will be created under /host-temp/buildah-build-cache/id
|
||||
id := ""
|
||||
//buidkit parity: cache directory defaults to 755
|
||||
mode := 0755
|
||||
//buidkit parity: cache directory defaults to uid 0 if not specified
|
||||
uid := 0
|
||||
//buidkit parity: cache directory defaults to gid 0 if not specified
|
||||
gid := 0
|
||||
|
||||
for _, val := range args {
|
||||
kv := strings.SplitN(val, "=", 2)
|
||||
switch kv[0] {
|
||||
case "nosuid", "nodev", "noexec":
|
||||
// TODO: detect duplication of these options.
|
||||
// (Is this necessary?)
|
||||
newMount.Options = append(newMount.Options, kv[0])
|
||||
case "rw", "readwrite":
|
||||
newMount.Options = append(newMount.Options, "rw")
|
||||
case "readonly", "ro":
|
||||
// Alias for "ro"
|
||||
newMount.Options = append(newMount.Options, "ro")
|
||||
setReadOnly = true
|
||||
case "shared", "rshared", "private", "rprivate", "slave", "rslave", "Z", "z", "U":
|
||||
newMount.Options = append(newMount.Options, kv[0])
|
||||
setShared = true
|
||||
case "bind-propagation":
|
||||
if len(kv) == 1 {
|
||||
return newMount, errors.Wrapf(optionArgError, kv[0])
|
||||
}
|
||||
newMount.Options = append(newMount.Options, kv[1])
|
||||
case "id":
|
||||
id = kv[1]
|
||||
case "target", "dst", "destination":
|
||||
if len(kv) == 1 {
|
||||
return newMount, errors.Wrapf(optionArgError, kv[0])
|
||||
}
|
||||
if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil {
|
||||
return newMount, err
|
||||
}
|
||||
newMount.Destination = kv[1]
|
||||
setDest = true
|
||||
case "mode":
|
||||
mode, err = strconv.Atoi(kv[1])
|
||||
if err != nil {
|
||||
return newMount, errors.Wrapf(err, "Unable to parse cache mode")
|
||||
}
|
||||
case "uid":
|
||||
uid, err = strconv.Atoi(kv[1])
|
||||
if err != nil {
|
||||
return newMount, errors.Wrapf(err, "Unable to parse cache uid")
|
||||
}
|
||||
case "gid":
|
||||
gid, err = strconv.Atoi(kv[1])
|
||||
if err != nil {
|
||||
return newMount, errors.Wrapf(err, "Unable to parse cache gid")
|
||||
}
|
||||
default:
|
||||
return newMount, errors.Wrapf(errBadMntOption, kv[0])
|
||||
}
|
||||
}
|
||||
|
||||
if !setDest {
|
||||
return newMount, noDestError
|
||||
}
|
||||
|
||||
// since type is cache and cache can be resused by consecutive builds
|
||||
// create a common cache directory, which should persists on hosts within temp lifecycle
|
||||
// add subdirectory if specified
|
||||
if id != "" {
|
||||
newMount.Source = filepath.Join(GetTempDir(), BuildahCacheDir, id)
|
||||
} else {
|
||||
newMount.Source = filepath.Join(GetTempDir(), BuildahCacheDir)
|
||||
}
|
||||
// create cache on host if not present
|
||||
err = os.MkdirAll(newMount.Source, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return newMount, errors.Wrapf(err, "Unable to create build cache directory")
|
||||
}
|
||||
//buidkit parity: change uid and gid if specificed otheriwise keep `0`
|
||||
err = os.Chown(newMount.Source, uid, gid)
|
||||
if err != nil {
|
||||
return newMount, errors.Wrapf(err, "Unable to change uid,gid of cache directory")
|
||||
}
|
||||
|
||||
// buildkit parity: default sharing should be shared
|
||||
// unless specified
|
||||
if !setShared {
|
||||
newMount.Options = append(newMount.Options, "shared")
|
||||
}
|
||||
|
||||
// buildkit parity: cache must writable unless `ro` or `readonly` is configured explicitly
|
||||
if !setReadOnly {
|
||||
newMount.Options = append(newMount.Options, "rw")
|
||||
}
|
||||
|
||||
// buildkit parity: default bind option for cache must be `rbind`
|
||||
// since we are actually looking for arbitrary content under cache directory
|
||||
newMount.Options = append(newMount.Options, "rbind")
|
||||
|
||||
opts, err := parse.ValidateVolumeOpts(newMount.Options)
|
||||
if err != nil {
|
||||
return newMount, err
|
||||
}
|
||||
newMount.Options = opts
|
||||
|
||||
return newMount, nil
|
||||
}
|
||||
|
||||
// GetTmpfsMount parses a single tmpfs mount entry from the --mount flag
|
||||
func GetTmpfsMount(args []string) (specs.Mount, error) {
|
||||
newMount := specs.Mount{
|
||||
|
|
21
run_linux.go
21
run_linux.go
|
@ -2407,6 +2407,13 @@ func (b *Builder) runSetupRunMounts(mounts []string, secrets map[string]string,
|
|||
}
|
||||
finalMounts = append(finalMounts, *mount)
|
||||
mountTargets = append(mountTargets, mount.Destination)
|
||||
case "cache":
|
||||
mount, err := b.getCacheMount(tokens, rootUID, rootGID, processUID, processGID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
finalMounts = append(finalMounts, *mount)
|
||||
mountTargets = append(mountTargets, mount.Destination)
|
||||
default:
|
||||
return nil, nil, errors.Errorf("invalid mount type %q", kv[1])
|
||||
}
|
||||
|
@ -2450,6 +2457,20 @@ func (b *Builder) getTmpfsMount(tokens []string, rootUID, rootGID, processUID, p
|
|||
return &volumes[0], nil
|
||||
}
|
||||
|
||||
func (b *Builder) getCacheMount(tokens []string, rootUID, rootGID, processUID, processGID int) (*spec.Mount, error) {
|
||||
var optionMounts []specs.Mount
|
||||
mount, err := parse.GetCacheMount(tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optionMounts = append(optionMounts, mount)
|
||||
volumes, err := b.runSetupVolumeMounts(b.MountLabel, nil, optionMounts, rootUID, rootGID, processUID, processGID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &volumes[0], nil
|
||||
}
|
||||
|
||||
func getSecretMount(tokens []string, secrets map[string]string, mountlabel string, containerWorkingDir string, uidmap []spec.LinuxIDMapping, gidmap []spec.LinuxIDMapping) (*spec.Mount, error) {
|
||||
errInvalidSyntax := errors.New("secret should have syntax id=id[,target=path,required=bool,mode=uint,uid=uint,gid=uint")
|
||||
if len(tokens) == 0 {
|
||||
|
|
|
@ -3579,3 +3579,15 @@ _EOF
|
|||
expect_output --substring "certs"
|
||||
run_buildah rmi -f testbud
|
||||
}
|
||||
|
||||
@test "bud-with-mount-cache-like-buildkit" {
|
||||
skip_if_no_runtime
|
||||
skip_if_in_container
|
||||
# try writing something to persistant cache
|
||||
run_buildah build -t testbud --signature-policy ${TESTSDIR}/policy.json -f ${TESTSDIR}/bud/buildkit-mount/Dockerfilecachewrite
|
||||
# try reading something from persistant cache in a different build
|
||||
run_buildah build -t testbud2 --signature-policy ${TESTSDIR}/policy.json -f ${TESTSDIR}/bud/buildkit-mount/Dockerfilecacheread
|
||||
expect_output --substring "hello"
|
||||
run_buildah rmi -f testbud
|
||||
run_buildah rmi -f testbud2
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
FROM alpine
|
||||
RUN mkdir /test
|
||||
# use option z if selinux is enabled
|
||||
RUN --mount=type=cache,target=/test,z cat /test/world
|
|
@ -0,0 +1,4 @@
|
|||
FROM alpine
|
||||
RUN mkdir /test
|
||||
# use option z if selinux is enabled
|
||||
RUN --mount=type=cache,target=/test,z echo hello > /test/world
|
Loading…
Reference in New Issue