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:
Aditya Rajan 2021-10-07 13:57:17 +05:30
parent 982717a6c2
commit fc69aa68c2
No known key found for this signature in database
GPG Key ID: 8E5A8A19DF7C8673
7 changed files with 209 additions and 2 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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{

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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