Skip to content

Commit d0dd5ac

Browse files
authored
Merge pull request #3867 from HileQAQ/main
Support for building overlaybd images
2 parents 6667c5e + c097f43 commit d0dd5ac

File tree

14 files changed

+912
-4
lines changed

14 files changed

+912
-4
lines changed

cache/blobs.go

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
package cache
22

33
import (
4+
"bufio"
45
"context"
56
"fmt"
7+
"io"
68
"maps"
79
"os"
10+
"path"
811
"slices"
912
"strconv"
1013

14+
obdlabel "github.com/containerd/accelerated-container-image/pkg/label"
15+
obdcmd "github.com/containerd/accelerated-container-image/pkg/utils"
16+
"github.com/containerd/containerd/v2/core/content"
1117
"github.com/containerd/containerd/v2/core/diff"
1218
"github.com/containerd/containerd/v2/core/leases"
1319
"github.com/containerd/containerd/v2/core/mount"
@@ -108,7 +114,6 @@ func computeBlobChain(ctx context.Context, sr *immutableRef, createIfNeeded bool
108114
}()
109115

110116
compressorFunc, finalize := comp.Type.Compress(ctx, comp)
111-
mediaType := comp.Type.MediaType()
112117

113118
var lowerRef *immutableRef
114119
switch sr.kind() {
@@ -180,6 +185,21 @@ func computeBlobChain(ctx context.Context, sr *immutableRef, createIfNeeded bool
180185
enableOverlay = false
181186
}
182187
}
188+
189+
mediaType := comp.Type.MediaType()
190+
if sr.cm.Snapshotter.Name() == "overlaybd" {
191+
snStat, err := sr.cm.Snapshotter.Stat(ctx, sr.getSnapshotID())
192+
if err != nil {
193+
return nil, errors.Wrapf(err, "failed to Stat overlaybd")
194+
}
195+
if bdPath := snStat.Labels[obdlabel.LocalOverlayBDPath]; bdPath != "" {
196+
if err := commitOverlayBD(ctx, sr, &desc); err != nil {
197+
return nil, err
198+
}
199+
mediaType = desc.MediaType
200+
enableOverlay = false
201+
}
202+
}
183203
if enableOverlay {
184204
computed, ok, err := sr.tryComputeOverlayBlob(ctx, lower, upper, mediaType, sr.ID(), compressorFunc)
185205
if !ok || err != nil {
@@ -497,3 +517,50 @@ func ensureCompression(ctx context.Context, ref *immutableRef, comp compression.
497517
}
498518
return nil
499519
}
520+
521+
func commitOverlayBD(ctx context.Context, sr *immutableRef, desc *ocispecs.Descriptor) error {
522+
snStat, err := sr.cm.Snapshotter.Stat(ctx, sr.getSnapshotID())
523+
if err != nil {
524+
return errors.Wrapf(err, "failed to Stat overlaybd")
525+
}
526+
bdPath := snStat.Labels[obdlabel.LocalOverlayBDPath]
527+
if bdPath == "" {
528+
return errors.New("missing overlaybd path label")
529+
}
530+
dir := path.Dir(bdPath)
531+
commitPath := path.Join(dir, "overlaybd.commit")
532+
err = obdcmd.Commit(ctx, dir, dir, true, "-t", "-z", "-f")
533+
if err != nil {
534+
return errors.Wrapf(err, "failed to overlaybd-commit")
535+
}
536+
cw, err := sr.cm.ContentStore.Writer(ctx, content.WithRef(sr.ID()))
537+
if err != nil {
538+
return errors.Wrapf(err, "failed to open writer")
539+
}
540+
fi, err := os.Open(commitPath)
541+
if err != nil {
542+
return errors.Wrapf(err, "failed to open overlaybd commit file")
543+
}
544+
sz, err := io.Copy(cw, bufio.NewReader(fi))
545+
if err != nil {
546+
return errors.Wrapf(err, "failed to do io.Copy()")
547+
}
548+
dgst := cw.Digest()
549+
labels := map[string]string{
550+
labels.LabelUncompressed: dgst.String(),
551+
obdlabel.OverlayBDBlobDigest: dgst.String(),
552+
obdlabel.OverlayBDBlobSize: fmt.Sprintf("%d", sz),
553+
}
554+
err = cw.Commit(ctx, sz, dgst, content.WithLabels(labels))
555+
if err != nil {
556+
return errors.Wrapf(err, "failed to do cw.Commit")
557+
}
558+
desc.Digest = dgst
559+
desc.Size = sz
560+
desc.MediaType = ocispecs.MediaTypeImageLayer
561+
desc.Annotations = map[string]string{
562+
obdlabel.OverlayBDBlobDigest: string(desc.Digest),
563+
obdlabel.OverlayBDBlobSize: fmt.Sprintf("%d", desc.Size),
564+
}
565+
return nil
566+
}

cache/manager.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ import (
1010
"sync"
1111
"time"
1212

13+
obdlabel "github.com/containerd/accelerated-container-image/pkg/label"
1314
"github.com/containerd/containerd/v2/core/content"
1415
"github.com/containerd/containerd/v2/core/diff"
1516
"github.com/containerd/containerd/v2/core/leases"
17+
"github.com/containerd/containerd/v2/core/snapshots"
1618
"github.com/containerd/containerd/v2/pkg/filters"
1719
"github.com/containerd/containerd/v2/pkg/gc"
1820
"github.com/containerd/containerd/v2/pkg/labels"
@@ -628,6 +630,10 @@ func (cm *cacheManager) New(ctx context.Context, s ImmutableRef, sess session.Gr
628630
}); rerr != nil {
629631
return nil, rerr
630632
}
633+
} else if cm.Snapshotter.Name() == "overlaybd" && parent != nil {
634+
// Snapshotter will create a R/W block device directly as rootfs with this label
635+
rwLabels := map[string]string{obdlabel.SupportReadWriteMode: "dev"}
636+
err = cm.Snapshotter.Prepare(ctx, snapshotID, parentSnapshotID, snapshots.WithLabels(rwLabels))
631637
} else {
632638
err = cm.Snapshotter.Prepare(ctx, snapshotID, parentSnapshotID)
633639
}

cache/refs.go

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"sync"
1212
"time"
1313

14+
obdlabel "github.com/containerd/accelerated-container-image/pkg/label"
1415
"github.com/containerd/containerd/v2/core/content"
1516
"github.com/containerd/containerd/v2/core/images"
1617
"github.com/containerd/containerd/v2/core/leases"
@@ -45,7 +46,7 @@ import (
4546
"golang.org/x/sync/errgroup"
4647
)
4748

48-
var additionalAnnotations = append(compression.EStargzAnnotations, labels.LabelUncompressed)
49+
var additionalAnnotations = append(append(compression.EStargzAnnotations, obdlabel.OverlayBDAnnotations...), labels.LabelUncompressed)
4950

5051
// Ref is a reference to cacheable objects.
5152
type Ref interface {
@@ -423,7 +424,11 @@ func (cr *cacheRecord) mount(ctx context.Context) (_ snapshot.Mountable, rerr er
423424
// Return the mount direct from View rather than setting it using the Mounts call below.
424425
// The two are equivalent for containerd snapshotters but the moby snapshotter requires
425426
// the use of the mountable returned by View in this case.
426-
mnts, err := cr.cm.Snapshotter.View(ctx, mountSnapshotID, cr.getSnapshotID())
427+
labels := make(map[string]string)
428+
if cr.cm.Snapshotter.Name() == "overlaybd" {
429+
labels["containerd.io/snapshot/overlaybd.writable"] = "dev"
430+
}
431+
mnts, err := cr.cm.Snapshotter.View(ctx, mountSnapshotID, cr.getSnapshotID(), snapshots.WithLabels(labels))
427432
if err != nil && !cerrdefs.IsAlreadyExists(err) {
428433
return nil, err
429434
}
@@ -1018,6 +1023,10 @@ func (sr *immutableRef) Extract(ctx context.Context, s session.Group) (rerr erro
10181023
return err
10191024
}
10201025
return rerr
1026+
} else if sr.cm.Snapshotter.Name() == "overlaybd" {
1027+
if rerr = sr.prepareRemoteSnapshotsOverlaybdMode(ctx); rerr == nil {
1028+
return sr.unlazy(ctx, sr.descHandlers, sr.progress, s, true, false)
1029+
}
10211030
}
10221031

10231032
return sr.unlazy(ctx, sr.descHandlers, sr.progress, s, true, false)
@@ -1143,6 +1152,55 @@ func (sr *immutableRef) prepareRemoteSnapshotsStargzMode(ctx context.Context, s
11431152
return err
11441153
}
11451154

1155+
func (sr *immutableRef) prepareRemoteSnapshotsOverlaybdMode(ctx context.Context) error {
1156+
_, err := g.Do(ctx, sr.ID()+"-prepare-remote-snapshot", func(ctx context.Context) (_ *leaseutil.LeaseRef, rerr error) {
1157+
dhs := sr.descHandlers
1158+
for _, r := range sr.layerChain() {
1159+
r := r
1160+
snapshotID := r.getSnapshotID()
1161+
if _, err := r.cm.Snapshotter.Stat(ctx, snapshotID); err == nil {
1162+
continue
1163+
}
1164+
dh := dhs[digest.Digest(r.getBlob())]
1165+
if dh == nil {
1166+
// We cannot prepare remote snapshots without descHandler.
1167+
return nil, nil
1168+
}
1169+
defaultLabels := snapshots.FilterInheritedLabels(dh.SnapshotLabels)
1170+
if defaultLabels == nil {
1171+
defaultLabels = make(map[string]string)
1172+
}
1173+
defaultLabels["containerd.io/snapshot.ref"] = snapshotID
1174+
// Prepare remote snapshots
1175+
var (
1176+
key = fmt.Sprintf("tmp-%s %s", identity.NewID(), r.getChainID())
1177+
opts = []snapshots.Opt{
1178+
snapshots.WithLabels(defaultLabels),
1179+
}
1180+
)
1181+
parentID := ""
1182+
if r.layerParent != nil {
1183+
parentID = r.layerParent.getSnapshotID()
1184+
}
1185+
if err := r.cm.Snapshotter.Prepare(ctx, key, parentID, opts...); err != nil {
1186+
if cerrdefs.IsAlreadyExists(err) {
1187+
// Check if the targeting snapshot ID has been prepared as
1188+
// a remote snapshot in the snapshotter.
1189+
_, err := r.cm.Snapshotter.Stat(ctx, snapshotID)
1190+
if err == nil { // usable as remote snapshot without unlazying.
1191+
// Try the next layer as well.
1192+
continue
1193+
}
1194+
}
1195+
}
1196+
// This layer and all upper layers cannot be prepared without unlazying.
1197+
break
1198+
}
1199+
return nil, nil
1200+
})
1201+
return err
1202+
}
1203+
11461204
func makeTmpLabelsStargzMode(labels map[string]string, s session.Group) (fields []string, res map[string]string) {
11471205
res = make(map[string]string)
11481206
// Append unique ID to labels for avoiding collision of labels among calls
@@ -1317,7 +1375,12 @@ func (sr *immutableRef) unlazyLayer(ctx context.Context, dhs DescHandlers, pg pr
13171375

13181376
key := fmt.Sprintf("extract-%s %s", identity.NewID(), sr.getChainID())
13191377

1320-
err = sr.cm.Snapshotter.Prepare(ctx, key, parentID)
1378+
if sr.cm.Snapshotter.Name() == "overlaybd" {
1379+
err = sr.cm.Snapshotter.Prepare(ctx, key, parentID,
1380+
snapshots.WithLabels(map[string]string{"containerd.io/snapshot.ref": string(sr.getChainID())}))
1381+
} else {
1382+
err = sr.cm.Snapshotter.Prepare(ctx, key, parentID)
1383+
}
13211384
if err != nil {
13221385
return err
13231386
}

cache/remotecache/registry/registry.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/containerd/containerd/v2/core/content"
99
"github.com/containerd/containerd/v2/core/remotes/docker"
1010
"github.com/containerd/containerd/v2/core/snapshots"
11+
"github.com/containerd/containerd/v2/pkg/snapshotters"
12+
1113
"github.com/distribution/reference"
1214
"github.com/moby/buildkit/cache/remotecache"
1315
"github.com/moby/buildkit/session"
@@ -167,6 +169,7 @@ func (dsl *withDistributionSourceLabel) SnapshotLabels(descs []ocispecs.Descript
167169
labels = make(map[string]string)
168170
}
169171
maps.Copy(labels, estargz.SnapshotLabels(dsl.ref, descs, index))
172+
labels[snapshotters.TargetRefLabel] = dsl.ref
170173
return labels
171174
}
172175

docs/overlaybd.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Overlaybd
2+
3+
[Overlaybd](https://github.com/containerd/overlaybd) is a novel layering block-level image format, which is design for container, secure container and applicable to virtual machine. And it is an open-source implementation of paper [DADI: Block-Level Image Service for Agile and Elastic Application Deployment. USENIX ATC'20"](https://www.usenix.org/conference/atc20/presentation/li-huiba).
4+
5+
## Build Overlaybd Images
6+
7+
Before building overlaybd images, ensure that `overlaybd-tcmu` and `overlaybd-snapshotter` are active by referring to the [QUICKSTART](https://github.com/containerd/accelerated-container-image/blob/main/docs/QUICKSTART.md#install) guide.
8+
9+
To use buildkit to build overlaybd images, you should specify `--oci-worker-snapshotter=overlaybd` and `--oci-worker-proxy-snapshotter-path=/run/overlaybd-snapshotter/overlaybd.sock` when start buildkitd:
10+
11+
```bash
12+
buildkitd --oci-worker-snapshotter=overlaybd --oci-worker-proxy-snapshotter-path=/run/overlaybd-snapshotter/overlaybd.sock
13+
```
14+
If an overlaybd image is used in the FROM instruction of a Dockerfile, the build will produce a new overlaybd image. It is essential to include `--oci-mediatypes=true` and `--compression=uncompressed` while running buildctl:
15+
16+
```bash
17+
buildctl build ... \
18+
--output type=image,name=<new image name>,push=true,oci-mediatypes=true,compression=uncompressed
19+
```
20+
21+
## Performance
22+
23+
In our test case Dockerfile, we used a 5GB OCI image (and corresponding overlaybd format), wrote some new layers of identical size, and recorded the time cost of image pull (as **pull** in the table below), building all lines in Dockerfile (as **build**), and exporting to image and pushing (as **push**).
24+
25+
OCI:
26+
27+
| **size per layer** | **layers** | **pull** | **build** | **push** | **total** |
28+
| -------- | ---- | ---- | ----- | ---- | ---- |
29+
| 4GB | 1 | 105.7| 23.5 | 219.4| 348.6|
30+
| 1GB | 4 | 88.5 | 34.0 | 123.8| 246.3|
31+
| 256MB | 10 | 92.1 | 20.7 | 63.6 | 176.4|
32+
33+
Overlaybd:
34+
35+
| **size per layer** | **layers** | **pull** | **build** | **push** | **total** |
36+
| -------- | ---- | ---- | ----- | ---- | ---- |
37+
| 4GB | 1 | 0.9 | 21.5 | 166.2| 188.6|
38+
| 1GB | 4 | 0.9 | 24.9 | 72.9 | 98.7 |
39+
| 256MB | 10 | 0.7 | 18.4 | 48.9 | 68.0 |

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/aws/aws-sdk-go-v2/credentials v1.17.27
1616
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.8
1717
github.com/aws/aws-sdk-go-v2/service/s3 v1.58.2
18+
github.com/containerd/accelerated-container-image v1.2.3
1819
github.com/containerd/console v1.0.4
1920
github.com/containerd/containerd/api v1.8.0
2021
github.com/containerd/containerd/v2 v2.0.5

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUo
9191
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
9292
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
9393
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
94+
github.com/containerd/accelerated-container-image v1.2.3 h1:tAIoP7Z7b2xGhb7QCM5Fa+2xqWfPqRmyi5lodbsGGRA=
95+
github.com/containerd/accelerated-container-image v1.2.3/go.mod h1:EvKVWor6ZQNUyYp0MZm5hw4k21ropuz7EegM+m/Jb/Q=
9496
github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
9597
github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
9698
github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=

source/containerimage/pull.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/containerd/containerd/v2/core/remotes"
1414
"github.com/containerd/containerd/v2/core/remotes/docker"
1515
"github.com/containerd/containerd/v2/core/snapshots"
16+
"github.com/containerd/containerd/v2/pkg/snapshotters"
1617
cerrdefs "github.com/containerd/errdefs"
1718
"github.com/moby/buildkit/cache"
1819
"github.com/moby/buildkit/client"
@@ -152,6 +153,7 @@ func (p *puller) CacheKey(ctx context.Context, g session.Group, index int) (cach
152153
labels = make(map[string]string)
153154
}
154155
maps.Copy(labels, estargz.SnapshotLabels(p.manifest.Ref, p.manifest.Descriptors, i))
156+
labels[snapshotters.TargetRefLabel] = p.manifest.Ref
155157

156158
p.descHandlers[desc.Digest] = &cache.DescHandler{
157159
Provider: p.manifest.Provider,

0 commit comments

Comments
 (0)