Skip to content

Commit

Permalink
Compute diff from the upper dir of overlayfs-based snapshotter
Browse files Browse the repository at this point in the history
Signed-off-by: ktock <ktokunaga.mail@gmail.com>
  • Loading branch information
ktock committed Jun 16, 2021
1 parent 0164c06 commit 8cd99ac
Show file tree
Hide file tree
Showing 250 changed files with 4,435 additions and 898 deletions.
9 changes: 9 additions & 0 deletions cache/blobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
imagespecidentity "github.com/opencontainers/image-spec/identity"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
)

Expand Down Expand Up @@ -73,6 +74,14 @@ func computeBlobChain(ctx context.Context, sr *immutableRef, createIfNeeded bool
var descr ocispec.Descriptor
var err error

if descr.Digest == "" && !isTypeWindows(sr) {
// Try optimized diff for overlayfs
descr, err = sr.computeOverlayBlob(ctx, mediaType, sr.ID(), s)
if err != nil {
logrus.Errorf("failed to compute blob (%s) from diff of overlay snapshotter: %+v", sr.ID(), err)
}
}

if descr.Digest == "" {
// reference needs to be committed
var lower []mount.Mount
Expand Down
196 changes: 196 additions & 0 deletions cache/blobs_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// +build !windows

package cache

import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"syscall"

"github.com/containerd/containerd/archive"
ctdcompression "github.com/containerd/containerd/archive/compression"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/mount"
"github.com/containerd/continuity/devices"
"github.com/containerd/continuity/fs"
"github.com/containerd/continuity/sysx"
"github.com/moby/buildkit/session"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)

const containerdUpperDirLabelKey = "containerd.io/snapshot/overlay.upperdir"

var emptyDesc = ocispec.Descriptor{}

func (sr *immutableRef) computeOverlayBlob(ctx context.Context, mediaType string, ref string, s session.Group) (_ ocispec.Descriptor, err error) {
sinfo, err := sr.cm.Snapshotter.Stat(ctx, getSnapshotID(sr.md))
if err != nil {
return emptyDesc, err
}
upper, ok := sinfo.Labels[containerdUpperDirLabelKey]
if !ok {
return emptyDesc, fmt.Errorf("upper directory is not registered")
}

var isCompressed bool
switch mediaType {
case ocispec.MediaTypeImageLayer:
case ocispec.MediaTypeImageLayerGzip:
isCompressed = true
default:
return emptyDesc, fmt.Errorf("unsupported diff media type: %v", mediaType)
}

cw, err := sr.cm.ContentStore.Writer(ctx,
content.WithRef(ref),
content.WithDescriptor(ocispec.Descriptor{
MediaType: mediaType, // most contentstore implementations just ignore this
}))
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to open writer")
}
defer func() {
if err != nil {
cw.Close()
}
}()

var lower []mount.Mount
if sr.parent != nil {
m, err := sr.parent.Mount(ctx, true, s)
if err != nil {
return emptyDesc, err
}
var release func() error
lower, release, err = m.Mount()
if err != nil {
return emptyDesc, err
}
if release != nil {
defer release()
}
}

var labels map[string]string
if isCompressed {
dgstr := digest.SHA256.Digester()
compressed, err := ctdcompression.CompressStream(cw, ctdcompression.Gzip)
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get compressed stream")
}
err = writeOverlayUpperdir(ctx, io.MultiWriter(compressed, dgstr.Hash()), upper, lower)
compressed.Close()
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to write compressed diff")
}
if labels == nil {
labels = map[string]string{}
}
labels[containerdUncompressed] = dgstr.Digest().String()
} else {
if err = writeOverlayUpperdir(ctx, cw, upper, lower); err != nil {
return emptyDesc, errors.Wrap(err, "failed to write diff")
}
}

var commitopts []content.Opt
if labels != nil {
commitopts = append(commitopts, content.WithLabels(labels))
}
dgst := cw.Digest()
if err := cw.Commit(ctx, 0, dgst, commitopts...); err != nil {
if !errdefs.IsAlreadyExists(err) {
return emptyDesc, errors.Wrap(err, "failed to commit")
}
}
cinfo, err := sr.cm.ContentStore.Info(ctx, dgst)
if err != nil {
return emptyDesc, errors.Wrap(err, "failed to get info from content store")
}
if cinfo.Labels == nil {
cinfo.Labels = make(map[string]string)
}
// Set uncompressed label if digest already existed without label
if _, ok := cinfo.Labels[containerdUncompressed]; !ok {
cinfo.Labels[containerdUncompressed] = labels[containerdUncompressed]
if _, err := sr.cm.ContentStore.Update(ctx, cinfo, "labels."+containerdUncompressed); err != nil {
return emptyDesc, errors.Wrap(err, "error setting uncompressed label")
}
}

return ocispec.Descriptor{
MediaType: mediaType,
Size: cinfo.Size,
Digest: cinfo.Digest,
}, nil
}

func writeOverlayUpperdir(ctx context.Context, w io.Writer, upper string, lower []mount.Mount) error {
cw := archive.NewChangeWriter(w, upper)
changeFn := cw.HandleChange
err := mount.WithTempMount(ctx, lower, func(lowerRoot string) error {
return fs.DiffDirChanges(ctx, changeFn, lowerRoot, &fs.DiffDirOptions{
DiffDir: upper,
DeleteChange: overlayDeleteChange,
})
})
if err != nil {
return err
}
return cw.Close()
}

func overlayDeleteChange(diffDir string, path string, base string, f os.FileInfo) (deleteFile string, skip bool, err error) {
// Check if this is a whiteout
if f.Mode()&os.ModeCharDevice != 0 {
if _, ok := f.Sys().(*syscall.Stat_t); ok {
maj, min, err := devices.DeviceInfo(f)
if err != nil {
return "", false, err
}
if maj == 0 && min == 0 {
// This file is deleted from base directory
if _, err := os.Lstat(filepath.Join(base, path)); err != nil {
if !os.IsNotExist(err) {
return "", false, err
}
// This file doesn't exist even in the base dir. We don't need whiteout. Just skip this file.
return "", true, nil
}
return path, false, nil
}
}
}

// Check if this is an opaque directory
if f.IsDir() {
for _, oKey := range []string{"trusted.overlay.opaque", "user.overlay.opaque"} {
opaque, err := sysx.LGetxattr(filepath.Join(diffDir, path), oKey)
if err != nil && err != unix.ENODATA {
return "", false, errors.Wrapf(err, "failed to retrieve trusted.overlay.opaque attr")
} else if len(opaque) == 1 && opaque[0] == 'y' {
// Add this directory and an opaque whiteout file.
if _, err := os.Lstat(filepath.Join(base, path)); err != nil {
if !os.IsNotExist(err) {
return "", false, err
}
// This file doesn't exist even in the base dir. We don't need whiteout.
// But this directory needs to be created.
return "", false, nil
}
// NOTE: This is a hack to let HandleChange create an opaque entry (".wh..wh..opq").
// HandleChange creates a whiteout named "<path>/.wh.<filename>" so we pass ".wh..opq" as filename here.
return filepath.Join(path, ".wh..opq"), false, nil
}
}
}

return "", false, nil
}
14 changes: 14 additions & 0 deletions cache/blobs_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// +build windows

package cache

import (
"context"

"github.com/moby/buildkit/session"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

func (sr *immutableRef) computeOverlayBlob(ctx context.Context, mediaType string, ref string, s session.Group) (ocispec.Descriptor, error) {
return ocispec.Descriptor{}, nil
}
15 changes: 9 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ go 1.13

require (
github.com/BurntSushi/toml v0.3.1
github.com/Microsoft/go-winio v0.4.17
github.com/Microsoft/hcsshim v0.8.16
github.com/Microsoft/go-winio v0.5.0
github.com/Microsoft/hcsshim v0.8.17
github.com/containerd/console v1.0.2
github.com/containerd/containerd v1.5.2
github.com/containerd/continuity v0.1.0
Expand Down Expand Up @@ -39,9 +39,9 @@ require (
github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc v1.0.0-rc93
github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d
github.com/opencontainers/selinux v1.8.0
github.com/opencontainers/runc v1.0.0-rc95
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417
github.com/opencontainers/selinux v1.8.2
github.com/pkg/errors v0.9.1
github.com/pkg/profile v1.5.0
github.com/serialx/hashring v0.0.0-20190422032157-8b2912629002
Expand All @@ -61,13 +61,16 @@ require (
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a
google.golang.org/grpc v1.37.0
)

replace (
// Patched version of containerd and continuity for overlayfs-optimized differ
github.com/containerd/containerd => github.com/ktock/containerd v1.2.1-0.20210615124510-50ed319ed864
github.com/containerd/continuity => github.com/ktock/continuity v0.1.1-0.20210616010202-ce76fccd7658
github.com/docker/docker => github.com/docker/docker v20.10.3-0.20210609100121-ef4d47340142+incompatible
github.com/hashicorp/go-immutable-radix => github.com/tonistiigi/go-immutable-radix v0.0.0-20170803185627-826af9ccf0fe
)
Loading

0 comments on commit 8cd99ac

Please sign in to comment.