Skip to content

Commit 9e031a2

Browse files
committed
Add support for storage-opt size with the windows snapshotter
This is primarily motivated by the desire to create windows containers with the io.containerd.snapshotter.v1 storage driver with a rootfs size larger than the default quota of 20G. Signed-off-by: Matthew Endsley <mendsley@gmail.com>
1 parent b0e8932 commit 9e031a2

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

daemon/containerd/image_snapshot.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package containerd
33
import (
44
"context"
55
"fmt"
6+
"strconv"
7+
"strings"
68

79
c8dimages "github.com/containerd/containerd/v2/core/images"
810
"github.com/containerd/containerd/v2/core/leases"
@@ -15,6 +17,7 @@ import (
1517
"github.com/docker/docker/errdefs"
1618
"github.com/docker/docker/image"
1719
"github.com/docker/docker/layer"
20+
"github.com/docker/go-units"
1821
"github.com/opencontainers/image-spec/identity"
1922
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2023
"github.com/pkg/errors"
@@ -44,6 +47,39 @@ func (i *ImageService) CreateLayerFromImage(img *image.Image, layerName string,
4447
return i.createLayer(descriptor, layerName, rwLayerOpts, nil)
4548
}
4649

50+
func (i *ImageService) generatePrepareOpts(ctx context.Context, rwLayerOpts *layer.CreateRWLayerOpts) ([]snapshots.Opt, error) {
51+
var opts []snapshots.Opt
52+
53+
if rwLayerOpts != nil && len(rwLayerOpts.StorageOpt) > 0 {
54+
for key, val := range rwLayerOpts.StorageOpt {
55+
key = strings.ToLower(key)
56+
switch key {
57+
case "size":
58+
size, err := units.RAMInBytes(val)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
if storageDriver := i.StorageDriver(); storageDriver == "windows" {
64+
sizeLabel := map[string]string{
65+
"containerd.io/snapshot/windows/rootfs.sizebytes": strconv.FormatInt(size, 10),
66+
}
67+
opts = append(opts, snapshots.WithLabels(sizeLabel))
68+
} else {
69+
/// TODO: containerd doesn't handle quotas for most snapshotters
70+
/// See: https://github.com/containerd/containerd/issues/759 and related
71+
log.G(ctx).Warnf("--storage-opt is not supported for the %s driver", storageDriver)
72+
}
73+
74+
default:
75+
return nil, fmt.Errorf("Unknown option %s", key)
76+
}
77+
}
78+
}
79+
80+
return opts, nil
81+
}
82+
4783
func (i *ImageService) createLayer(descriptor *ocispec.Descriptor, layerName string, rwLayerOpts *layer.CreateRWLayerOpts, initFunc layer.MountInit) (container.RWLayer, error) {
4884
ctx := context.TODO()
4985
var parentSnapshot string
@@ -78,7 +114,13 @@ func (i *ImageService) createLayer(descriptor *ocispec.Descriptor, layerName str
78114
if !i.idMapping.Empty() {
79115
err = i.remapSnapshot(ctx, sn, layerName, parentSnapshot)
80116
} else {
81-
_, err = sn.Prepare(ctx, layerName, parentSnapshot)
117+
var sopts []snapshots.Opt
118+
sopts, err = i.generatePrepareOpts(ctx, rwLayerOpts)
119+
if err != nil {
120+
return nil, err
121+
}
122+
123+
_, err = sn.Prepare(ctx, layerName, parentSnapshot, sopts...)
82124
}
83125

84126
if err != nil {

integration/container/create_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/docker/docker/oci"
2222
"github.com/docker/docker/pkg/stringid"
2323
"github.com/docker/docker/testutil"
24+
"github.com/docker/go-units"
2425
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2526
"gotest.tools/v3/assert"
2627
is "gotest.tools/v3/assert/cmp"
@@ -790,3 +791,52 @@ func TestContainerdContainerImageInfo(t *testing.T) {
790791
assert.Equal(t, ctr.Image, "")
791792
}
792793
}
794+
795+
func TestContainerdSnapshotQuota(t *testing.T) {
796+
skip.If(t, testEnv.DaemonInfo.OSType != "windows", "Only test windows")
797+
798+
ctx := setupTest(t)
799+
800+
apiClient := testEnv.APIClient()
801+
defer apiClient.Close()
802+
803+
info, err := apiClient.Info(ctx)
804+
assert.NilError(t, err)
805+
806+
skip.If(t, info.Containerd == nil, "requires containerd")
807+
skip.If(t, info.Driver != "windows", "requires windows daemon")
808+
foundSnapshotter := false
809+
for _, pair := range info.DriverStatus {
810+
if pair[0] == "driver-type" && pair[1] == "io.containerd.snapshotter.v1" {
811+
foundSnapshotter = true
812+
}
813+
}
814+
skip.If(t, !foundSnapshotter, "requires snapshotter driver")
815+
816+
const containerSize = "32G"
817+
expectedSize, err := units.RAMInBytes(containerSize)
818+
assert.NilError(t, err)
819+
expectedSizeStr := strconv.FormatInt(expectedSize, 10)
820+
821+
id := testContainer.Create(ctx, t, apiClient, func(cfg *testContainer.TestContainerConfig) {
822+
cfg.Config.Image = "mcr.microsoft.com/windows/servercore:ltsc2022"
823+
cfg.Config.Cmd = []string{"powershell", "-Command", "Start-Sleep -Seconds 20"}
824+
cfg.HostConfig.StorageOpt = make(map[string]string)
825+
cfg.HostConfig.StorageOpt["size"] = containerSize
826+
})
827+
defer apiClient.ContainerRemove(ctx, id, container.RemoveOptions{Force: true})
828+
829+
err = apiClient.ContainerStart(ctx, id, container.StartOptions{})
830+
assert.NilError(t, err)
831+
832+
c8dClient, err := containerd.New(info.Containerd.Address, containerd.WithDefaultNamespace(info.Containerd.Namespaces.Containers))
833+
assert.NilError(t, err)
834+
defer c8dClient.Close()
835+
836+
snapshotInfo, err := c8dClient.SnapshotService("windows").Stat(ctx, id)
837+
assert.NilError(t, err)
838+
839+
rootfsLabel, ok := snapshotInfo.Labels["containerd.io/snapshot/windows/rootfs.sizebytes"]
840+
assert.Assert(t, ok, "Snapshot does not cotain rootfs quota label")
841+
assert.Equal(t, rootfsLabel, expectedSizeStr, "Container snapshot size does not match")
842+
}

0 commit comments

Comments
 (0)