Browse code

c8d: Implement `RWLayer` and remove `PrepareSnapshot`

Implement containerd image store backed `RWLayer` and remove the
containerd-specific `PrepareSnapshot` method from the ImageService
interface.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2024/12/16 23:35:22
Showing 9 changed files
... ...
@@ -4,57 +4,55 @@ import (
4 4
 	"context"
5 5
 	"fmt"
6 6
 
7
-	"github.com/containerd/containerd"
8 7
 	c8dimages "github.com/containerd/containerd/images"
9 8
 	"github.com/containerd/containerd/leases"
10 9
 	"github.com/containerd/containerd/mount"
11 10
 	"github.com/containerd/containerd/snapshots"
12 11
 	cerrdefs "github.com/containerd/errdefs"
13
-	"github.com/containerd/platforms"
12
+	"github.com/containerd/log"
13
+	"github.com/docker/docker/container"
14
+	"github.com/docker/docker/daemon/snapshotter"
14 15
 	"github.com/docker/docker/errdefs"
16
+	"github.com/docker/docker/layer"
15 17
 	"github.com/opencontainers/image-spec/identity"
16
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
17 18
 	"github.com/pkg/errors"
18 19
 )
19 20
 
20
-// PrepareSnapshot prepares a snapshot from a parent image for a container
21
-func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *ocispec.Platform, setupInit func(string) error) error {
21
+// CreateLayer creates a new layer for a container.
22
+// TODO(vvoland): Decouple from container
23
+func (i *ImageService) CreateLayer(ctr *container.Container, initFunc layer.MountInit) (container.RWLayer, error) {
24
+	ctx := context.TODO()
25
+
22 26
 	var parentSnapshot string
23
-	if parentImage != "" {
24
-		img, err := i.resolveImage(ctx, parentImage)
27
+	if ctr.ImageManifest != nil {
28
+		img := c8dimages.Image{
29
+			Target: *ctr.ImageManifest,
30
+		}
31
+		platformImg, err := i.NewImageManifest(ctx, img, img.Target)
25 32
 		if err != nil {
26
-			return err
33
+			return nil, err
27 34
 		}
28
-
29
-		cs := i.content
30
-
31
-		matcher := i.matchRequestedOrDefault(platforms.Only, platform)
32
-
33
-		platformImg := containerd.NewImageWithPlatform(i.client, img, matcher)
34 35
 		unpacked, err := platformImg.IsUnpacked(ctx, i.snapshotter)
35 36
 		if err != nil {
36
-			return err
37
+			return nil, err
37 38
 		}
38 39
 
39 40
 		if !unpacked {
40 41
 			if err := platformImg.Unpack(ctx, i.snapshotter); err != nil {
41
-				return err
42
+				return nil, err
42 43
 			}
43 44
 		}
44 45
 
45
-		desc, err := c8dimages.Config(ctx, cs, img.Target, matcher)
46
+		diffIDs, err := platformImg.RootFS(ctx)
46 47
 		if err != nil {
47
-			return err
48
-		}
49
-
50
-		diffIDs, err := c8dimages.RootFS(ctx, cs, desc)
51
-		if err != nil {
52
-			return err
48
+			return nil, err
53 49
 		}
54 50
 
55 51
 		parentSnapshot = identity.ChainID(diffIDs).String()
56 52
 	}
57 53
 
54
+	id := ctr.ID
55
+
58 56
 	// TODO: Consider a better way to do this. It is better to have a container directly
59 57
 	// reference a snapshot, however, that is not done today because a container may
60 58
 	// removed and recreated with nothing holding the snapshot in between. Consider
... ...
@@ -63,22 +61,151 @@ func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentIma
63 63
 	ls := i.client.LeasesService()
64 64
 	lease, err := ls.Create(ctx, leases.WithID(id))
65 65
 	if err != nil {
66
-		return err
66
+		return nil, err
67 67
 	}
68 68
 	ctx = leases.WithLease(ctx, lease.ID)
69 69
 
70
-	snapshotter := i.client.SnapshotService(i.StorageDriver())
71
-
72
-	if err := i.prepareInitLayer(ctx, id, parentSnapshot, setupInit); err != nil {
73
-		return err
70
+	if err := i.prepareInitLayer(ctx, id, parentSnapshot, initFunc); err != nil {
71
+		return nil, err
74 72
 	}
75 73
 
74
+	sn := i.client.SnapshotService(i.StorageDriver())
76 75
 	if !i.idMapping.Empty() {
77
-		return i.remapSnapshot(ctx, snapshotter, id, id+"-init")
76
+		err = i.remapSnapshot(ctx, sn, id, id+"-init")
77
+	} else {
78
+		_, err = sn.Prepare(ctx, id, id+"-init")
79
+	}
80
+
81
+	if err != nil {
82
+		return nil, err
83
+	}
84
+
85
+	return &rwLayer{
86
+		id:              id,
87
+		snapshotterName: i.StorageDriver(),
88
+		snapshotter:     sn,
89
+		refCountMounter: i.refCountMounter,
90
+		lease:           lease,
91
+	}, nil
92
+}
93
+
94
+type rwLayer struct {
95
+	id              string
96
+	snapshotter     snapshots.Snapshotter
97
+	snapshotterName string
98
+	refCountMounter snapshotter.Mounter
99
+	root            string
100
+	lease           leases.Lease
101
+}
102
+
103
+func (l *rwLayer) mounts(ctx context.Context) ([]mount.Mount, error) {
104
+	return l.snapshotter.Mounts(ctx, l.id)
105
+}
106
+
107
+func (l *rwLayer) Mount(mountLabel string) (string, error) {
108
+	ctx := context.TODO()
109
+
110
+	// TODO: Investigate how we can handle mountLabel
111
+	_ = mountLabel
112
+	mounts, err := l.mounts(ctx)
113
+	if err != nil {
114
+		return "", err
115
+	}
116
+
117
+	var root string
118
+	if root, err = l.refCountMounter.Mount(mounts, l.id); err != nil {
119
+		return "", fmt.Errorf("failed to mount %s: %w", root, err)
120
+	}
121
+	l.root = root
122
+
123
+	log.G(ctx).WithFields(log.Fields{"container": l.id, "root": root, "snapshotter": l.snapshotterName}).Debug("container mounted via snapshotter")
124
+	return root, nil
125
+}
126
+
127
+// GetLayerByID returns a layer by ID
128
+// called from daemon.go Daemon.restore().
129
+func (i *ImageService) GetLayerByID(cid string) (container.RWLayer, error) {
130
+	ctx := context.TODO()
131
+
132
+	sn := i.client.SnapshotService(i.StorageDriver())
133
+	if _, err := sn.Stat(ctx, cid); err != nil {
134
+		if !cerrdefs.IsNotFound(err) {
135
+			return nil, fmt.Errorf("failed to stat snapshot %s: %w", cid, err)
136
+		}
137
+		return nil, errdefs.NotFound(fmt.Errorf("RW layer for container %s not found", cid))
138
+	}
139
+
140
+	ls := i.client.LeasesService()
141
+	lss, err := ls.List(ctx, "id=="+cid)
142
+	if err != nil {
143
+		return nil, err
144
+	}
145
+
146
+	switch len(lss) {
147
+	case 0:
148
+		return nil, errdefs.NotFound(errors.New("rw layer lease not found for container " + cid))
149
+	default:
150
+		log.G(ctx).WithFields(log.Fields{"container": cid, "leases": lss}).Warn("multiple leases with the same id found, this should not happen")
151
+	case 1:
152
+	}
153
+
154
+	root, err := i.refCountMounter.Mounted(cid)
155
+	if err != nil {
156
+		log.G(ctx).WithField("container", cid).Warn("failed to determine if container is already mounted")
157
+	}
158
+
159
+	return &rwLayer{
160
+		id:              cid,
161
+		snapshotterName: i.StorageDriver(),
162
+		snapshotter:     sn,
163
+		refCountMounter: i.refCountMounter,
164
+		lease:           lss[0],
165
+		root:            root,
166
+	}, nil
167
+
168
+}
169
+
170
+func (l *rwLayer) Unmount() error {
171
+	ctx := context.TODO()
172
+
173
+	if l.root == "" {
174
+		target, err := l.refCountMounter.Mounted(l.id)
175
+		if err != nil {
176
+			log.G(ctx).WithField("id", l.id).Warn("failed to determine if container is already mounted")
177
+		}
178
+		if target == "" {
179
+			return errors.New("layer not mounted")
180
+		}
181
+		l.root = target
78 182
 	}
79 183
 
80
-	_, err = snapshotter.Prepare(ctx, id, id+"-init")
81
-	return err
184
+	if err := l.refCountMounter.Unmount(l.root); err != nil {
185
+		log.G(ctx).WithField("container", l.id).WithError(err).Error("error unmounting container")
186
+		return fmt.Errorf("failed to unmount %s: %w", l.root, err)
187
+	}
188
+
189
+	return nil
190
+}
191
+
192
+func (l rwLayer) Metadata() (map[string]string, error) {
193
+	return nil, nil
194
+}
195
+
196
+// ReleaseLayer releases a layer allowing it to be removed
197
+// called from delete.go Daemon.cleanupContainer(), and Daemon.containerExport()
198
+func (i *ImageService) ReleaseLayer(rwlayer container.RWLayer) error {
199
+	c8dLayer, ok := rwlayer.(*rwLayer)
200
+	if !ok {
201
+		return fmt.Errorf("invalid layer type %T", rwlayer)
202
+	}
203
+
204
+	ls := i.client.LeasesService()
205
+	if err := ls.Delete(context.Background(), c8dLayer.lease, leases.SynchronousDelete); err != nil {
206
+		if !cerrdefs.IsNotFound(err) {
207
+			return err
208
+		}
209
+	}
210
+	return nil
82 211
 }
83 212
 
84 213
 func (i *ImageService) prepareInitLayer(ctx context.Context, id string, parent string, setupInit func(string) error) error {
... ...
@@ -11,42 +11,30 @@ import (
11 11
 
12 12
 // Mount mounts the container filesystem in a temporary location, use defer imageService.Unmount
13 13
 // to unmount the filesystem when calling this
14
-func (i *ImageService) Mount(ctx context.Context, container *container.Container) error {
15
-	snapshotter := i.client.SnapshotService(container.Driver)
16
-	mounts, err := snapshotter.Mounts(ctx, container.ID)
17
-	if err != nil {
18
-		return err
14
+func (i *ImageService) Mount(ctx context.Context, ctr *container.Container) error {
15
+	if ctr.RWLayer == nil {
16
+		return errors.New("RWLayer of container " + ctr.ID + " is unexpectedly nil")
19 17
 	}
20 18
 
21
-	var root string
22
-	if root, err = i.refCountMounter.Mount(mounts, container.ID); err != nil {
23
-		return fmt.Errorf("failed to mount %s: %w", root, err)
19
+	root, err := ctr.RWLayer.Mount(ctr.GetMountLabel())
20
+	if err != nil {
21
+		return fmt.Errorf("failed to mount container %s: %w", ctr.ID, err)
24 22
 	}
25 23
 
26
-	log.G(ctx).WithFields(log.Fields{"container": container.ID, "root": root, "snapshotter": container.Driver}).Debug("container mounted via snapshotter")
24
+	log.G(ctx).WithFields(log.Fields{"ctr": ctr.ID, "root": root, "snapshotter": ctr.Driver}).Debug("ctr mounted via snapshotter")
27 25
 
28
-	container.BaseFS = root
26
+	ctr.BaseFS = root
29 27
 	return nil
30 28
 }
31 29
 
32 30
 // Unmount unmounts the container base filesystem
33
-func (i *ImageService) Unmount(ctx context.Context, container *container.Container) error {
34
-	baseFS := container.BaseFS
35
-	if baseFS == "" {
36
-		target, err := i.refCountMounter.Mounted(container.ID)
37
-		if err != nil {
38
-			log.G(ctx).WithField("containerID", container.ID).Warn("failed to determine if container is already mounted")
39
-		}
40
-		if target == "" {
41
-			return errors.New("BaseFS is empty")
42
-		}
43
-		baseFS = target
31
+func (i *ImageService) Unmount(ctx context.Context, ctr *container.Container) error {
32
+	if ctr.RWLayer == nil {
33
+		return errors.New("RWLayer of container " + ctr.ID + " is unexpectedly nil")
44 34
 	}
45 35
 
46
-	if err := i.refCountMounter.Unmount(baseFS); err != nil {
47
-		log.G(ctx).WithField("container", container.ID).WithError(err).Error("error unmounting container")
48
-		return fmt.Errorf("failed to unmount %s: %w", baseFS, err)
36
+	if err := ctr.RWLayer.Unmount(); err != nil {
37
+		return fmt.Errorf("failed to unmount container %s: %w", ctr.ID, err)
49 38
 	}
50
-
51 39
 	return nil
52 40
 }
... ...
@@ -20,7 +20,6 @@ import (
20 20
 	dimages "github.com/docker/docker/daemon/images"
21 21
 	"github.com/docker/docker/daemon/snapshotter"
22 22
 	"github.com/docker/docker/errdefs"
23
-	"github.com/docker/docker/layer"
24 23
 	"github.com/docker/docker/pkg/idtools"
25 24
 	"github.com/docker/docker/registry"
26 25
 	"github.com/pkg/errors"
... ...
@@ -108,13 +107,6 @@ func (i *ImageService) CountImages(ctx context.Context) int {
108 108
 	return len(imgs)
109 109
 }
110 110
 
111
-// CreateLayer creates a filesystem layer for a container.
112
-// called from create.go
113
-// TODO: accept an opt struct instead of container?
114
-func (i *ImageService) CreateLayer(container *container.Container, initFunc layer.MountInit) (container.RWLayer, error) {
115
-	return nil, errdefs.NotImplemented(errdefs.NotImplemented(errors.New("not implemented")))
116
-}
117
-
118 111
 // LayerStoreStatus returns the status for each layer store
119 112
 // called from info.go
120 113
 func (i *ImageService) LayerStoreStatus() [][2]string {
... ...
@@ -143,12 +135,6 @@ func (i *ImageService) StorageDriver() string {
143 143
 	return i.snapshotter
144 144
 }
145 145
 
146
-// ReleaseLayer releases a layer allowing it to be removed
147
-// called from delete.go Daemon.cleanupContainer(), and Daemon.containerExport()
148
-func (i *ImageService) ReleaseLayer(rwlayer container.RWLayer) error {
149
-	return errdefs.NotImplemented(errors.New("not implemented"))
150
-}
151
-
152 146
 // LayerDiskUsage returns the number of bytes used by layer stores
153 147
 // called from disk_usage.go
154 148
 func (i *ImageService) LayerDiskUsage(ctx context.Context) (int64, error) {
... ...
@@ -2,6 +2,7 @@ package containerd
2 2
 
3 3
 import (
4 4
 	"context"
5
+	"fmt"
5 6
 
6 7
 	"github.com/docker/docker/container"
7 8
 	"github.com/docker/docker/image"
... ...
@@ -9,13 +10,17 @@ import (
9 9
 )
10 10
 
11 11
 // GetLayerFolders returns the layer folders from an image RootFS.
12
-func (i *ImageService) GetLayerFolders(img *image.Image, rwLayer container.RWLayer, containerID string) ([]string, error) {
13
-	if rwLayer != nil {
14
-		return nil, errors.New("RWLayer is unexpectedly not nil")
12
+func (i *ImageService) GetLayerFolders(img *image.Image, layer container.RWLayer, containerID string) ([]string, error) {
13
+	if layer == nil {
14
+		return nil, errors.New("RWLayer is unexpectedly nil")
15 15
 	}
16 16
 
17
-	snapshotter := i.client.SnapshotService(i.StorageDriver())
18
-	mounts, err := snapshotter.Mounts(context.TODO(), containerID)
17
+	c8dLayer, ok := layer.(*rwLayer)
18
+	if !ok {
19
+		return nil, fmt.Errorf("unexpected layer type: %T", layer)
20
+	}
21
+
22
+	mounts, err := c8dLayer.mounts(context.TODO())
19 23
 	if err != nil {
20 24
 		return nil, errors.Wrapf(err, "snapshotter.Mounts failed: container %s", containerID)
21 25
 	}
... ...
@@ -185,18 +185,12 @@ func (daemon *Daemon) create(ctx context.Context, daemonCfg *config.Config, opts
185 185
 	ctr.HostConfig.StorageOpt = opts.params.HostConfig.StorageOpt
186 186
 	ctr.ImageManifest = imgManifest
187 187
 
188
-	if daemon.UsesSnapshotter() {
189
-		if err := daemon.imageService.PrepareSnapshot(ctx, ctr.ID, opts.params.Config.Image, opts.params.Platform, setupInitLayer(daemon.idMapping)); err != nil {
190
-			return nil, err
191
-		}
192
-	} else {
193
-		// Set RWLayer for container after mount labels have been set
194
-		rwLayer, err := daemon.imageService.CreateLayer(ctr, setupInitLayer(daemon.idMapping))
195
-		if err != nil {
196
-			return nil, errdefs.System(err)
197
-		}
198
-		ctr.RWLayer = rwLayer
188
+	// Set RWLayer for container after mount labels have been set
189
+	rwLayer, err := daemon.imageService.CreateLayer(ctr, setupInitLayer(daemon.idMapping))
190
+	if err != nil {
191
+		return nil, errdefs.System(err)
199 192
 	}
193
+	ctr.RWLayer = rwLayer
200 194
 
201 195
 	current := idtools.CurrentIdentity()
202 196
 	if err := idtools.MkdirAndChown(ctr.Root, 0o710, idtools.Identity{UID: current.UID, GID: daemon.IdentityMapping().RootPair().GID}); err != nil {
... ...
@@ -202,11 +202,6 @@ func (daemon *Daemon) UsesSnapshotter() bool {
202 202
 	return daemon.usesSnapshotter
203 203
 }
204 204
 
205
-// layerAccessor may be implemented by ImageService
206
-type layerAccessor interface {
207
-	GetLayerByID(cid string) (container.RWLayer, error)
208
-}
209
-
210 205
 func (daemon *Daemon) restore(cfg *configStore) error {
211 206
 	var mapLock sync.Mutex
212 207
 	containers := make(map[string]*container.Container)
... ...
@@ -248,14 +243,12 @@ func (daemon *Daemon) restore(cfg *configStore) error {
248 248
 				logger.Debugf("not restoring container because it was created with another storage driver (%s)", c.Driver)
249 249
 				return
250 250
 			}
251
-			if accessor, ok := daemon.imageService.(layerAccessor); ok {
252
-				rwlayer, err := accessor.GetLayerByID(c.ID)
253
-				if err != nil {
254
-					logger.WithError(err).Error("failed to load container mount")
255
-					return
256
-				}
257
-				c.RWLayer = rwlayer
251
+			rwlayer, err := daemon.imageService.GetLayerByID(c.ID)
252
+			if err != nil {
253
+				logger.WithError(err).Error("failed to load container mount")
254
+				return
258 255
 			}
256
+			c.RWLayer = rwlayer
259 257
 			logger.WithFields(log.Fields{
260 258
 				"running": c.IsRunning(),
261 259
 				"paused":  c.IsPaused(),
... ...
@@ -8,8 +8,6 @@ import (
8 8
 	"strings"
9 9
 	"time"
10 10
 
11
-	"github.com/containerd/containerd/leases"
12
-	cerrdefs "github.com/containerd/errdefs"
13 11
 	"github.com/containerd/log"
14 12
 	"github.com/docker/docker/api/types/backend"
15 13
 	containertypes "github.com/docker/docker/api/types/container"
... ...
@@ -151,19 +149,6 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, config ba
151 151
 			container.SetRemovalError(err)
152 152
 			return err
153 153
 		}
154
-	} else {
155
-		if daemon.UsesSnapshotter() {
156
-			ls := daemon.containerdClient.LeasesService()
157
-			lease := leases.Lease{
158
-				ID: container.ID,
159
-			}
160
-			if err := ls.Delete(context.Background(), lease, leases.SynchronousDelete); err != nil {
161
-				if !cerrdefs.IsNotFound(err) {
162
-					container.SetRemovalError(err)
163
-					return err
164
-				}
165
-			}
166
-		}
167 154
 	}
168 155
 
169 156
 	// Hold the container lock while deleting the container root directory
... ...
@@ -45,14 +45,11 @@ type ImageService interface {
45 45
 	SquashImage(id, parent string) (string, error)
46 46
 	ImageInspect(ctx context.Context, refOrID string, opts backend.ImageInspectOpts) (*imagetype.InspectResponse, error)
47 47
 
48
-	// Containerd related methods
49
-
50
-	PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *ocispec.Platform, setupInit func(string) error) error
51
-
52 48
 	// Layers
53 49
 
54 50
 	GetImageAndReleasableLayer(ctx context.Context, refOrID string, opts backend.GetImageAndLayerOptions) (builder.Image, builder.ROLayer, error)
55 51
 	CreateLayer(container *container.Container, initFunc layer.MountInit) (container.RWLayer, error)
52
+	GetLayerByID(cid string) (container.RWLayer, error)
56 53
 	LayerStoreStatus() [][2]string
57 54
 	GetLayerMountID(cid string) (string, error)
58 55
 	ReleaseLayer(rwlayer container.RWLayer) error
... ...
@@ -45,11 +45,6 @@ type manifest struct {
45 45
 	Config ocispec.Descriptor `json:"config"`
46 46
 }
47 47
 
48
-func (i *ImageService) PrepareSnapshot(ctx context.Context, id string, parentImage string, platform *ocispec.Platform, setupInit func(string) error) error {
49
-	// Only makes sense when containerd image store is used
50
-	panic("not implemented")
51
-}
52
-
53 48
 func (i *ImageService) manifestMatchesPlatform(ctx context.Context, img *image.Image, platform ocispec.Platform) (bool, error) {
54 49
 	ls, err := i.leases.ListResources(ctx, leases.Lease{ID: imageKey(img.ID().String())})
55 50
 	if err != nil {