Skip to content
This repository was archived by the owner on Mar 9, 2022. It is now read-only.

Commit e571fd8

Browse files
committed
Limit value size of additional annotation for avoiding unpack failure
In containerd, there is a size limit for label size (4096 chars). Currently if an image has many layers (> (4096-39)/72 > 56), `containerd.io/snapshot/cri.image-layers` will hit the limit of label size and the unpack will fail. This commit fixes this by limiting the size of the annotation. Signed-off-by: Kohei Tokunaga <[email protected]>
1 parent 35e623e commit e571fd8

2 files changed

Lines changed: 75 additions & 11 deletions

File tree

pkg/server/image_pull.go

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/containerd/containerd"
3232
"github.com/containerd/containerd/errdefs"
3333
containerdimages "github.com/containerd/containerd/images"
34+
"github.com/containerd/containerd/labels"
3435
"github.com/containerd/containerd/log"
3536
distribution "github.com/containerd/containerd/reference/docker"
3637
"github.com/containerd/containerd/remotes/docker"
@@ -460,7 +461,7 @@ const (
460461
targetDigestLabel = "containerd.io/snapshot/cri.layer-digest"
461462
// targetImageLayersLabel is a label which contains layer digests contained in
462463
// the target image and will be passed to snapshotters for preparing layers in
463-
// parallel.
464+
// parallel. Skipping some layers is allowed and only affects performance.
464465
targetImageLayersLabel = "containerd.io/snapshot/cri.image-layers"
465466
)
466467

@@ -478,15 +479,6 @@ func appendInfoHandlerWrapper(ref string) func(f containerdimages.Handler) conta
478479
}
479480
switch desc.MediaType {
480481
case imagespec.MediaTypeImageManifest, containerdimages.MediaTypeDockerSchema2Manifest:
481-
var layers string
482-
for _, c := range children {
483-
if containerdimages.IsLayerType(c.MediaType) {
484-
layers += fmt.Sprintf("%s,", c.Digest.String())
485-
}
486-
}
487-
if len(layers) >= 1 {
488-
layers = layers[:len(layers)-1]
489-
}
490482
for i := range children {
491483
c := &children[i]
492484
if containerdimages.IsLayerType(c.MediaType) {
@@ -495,11 +487,33 @@ func appendInfoHandlerWrapper(ref string) func(f containerdimages.Handler) conta
495487
}
496488
c.Annotations[targetRefLabel] = ref
497489
c.Annotations[targetDigestLabel] = c.Digest.String()
498-
c.Annotations[targetImageLayersLabel] = layers
490+
c.Annotations[targetImageLayersLabel] = getLayers(ctx, targetImageLayersLabel, children[i:], labels.Validate)
499491
}
500492
}
501493
}
502494
return children, nil
503495
})
504496
}
505497
}
498+
499+
// getLayers returns comma-separated digests based on the passed list of
500+
// descriptors. The returned list contains as many digests as possible as well
501+
// as meets the label validation.
502+
func getLayers(ctx context.Context, key string, descs []imagespec.Descriptor, validate func(k, v string) error) (layers string) {
503+
var item string
504+
for _, l := range descs {
505+
if containerdimages.IsLayerType(l.MediaType) {
506+
item = l.Digest.String()
507+
if layers != "" {
508+
item = "," + item
509+
}
510+
// This avoids the label hits the size limitation.
511+
if err := validate(key, layers+item); err != nil {
512+
log.G(ctx).WithError(err).WithField("label", key).Debugf("%q is omitted in the layers list", l.Digest.String())
513+
break
514+
}
515+
layers += item
516+
}
517+
}
518+
return
519+
}

pkg/server/image_pull_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,14 @@
1717
package server
1818

1919
import (
20+
"context"
2021
"encoding/base64"
22+
"fmt"
23+
"strings"
2124
"testing"
2225

26+
digest "github.com/opencontainers/go-digest"
27+
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
2328
"github.com/stretchr/testify/assert"
2429
runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
2530

@@ -327,3 +332,48 @@ func TestEncryptedImagePullOpts(t *testing.T) {
327332
assert.Equal(t, test.expectedOpts, got)
328333
}
329334
}
335+
336+
func TestImageLayersLabel(t *testing.T) {
337+
sampleKey := "sampleKey"
338+
sampleDigest, err := digest.Parse("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
339+
assert.NoError(t, err)
340+
sampleMaxSize := 300
341+
sampleValidate := func(k, v string) error {
342+
if (len(k) + len(v)) > sampleMaxSize {
343+
return fmt.Errorf("invalid: %q: %q", k, v)
344+
}
345+
return nil
346+
}
347+
348+
tests := []struct {
349+
name string
350+
layersNum int
351+
wantNum int
352+
}{
353+
{
354+
name: "valid number of layers",
355+
layersNum: 2,
356+
wantNum: 2,
357+
},
358+
{
359+
name: "many layers",
360+
layersNum: 5, // hits sampleMaxSize (300 chars).
361+
wantNum: 4, // layers should be ommitted for avoiding invalid label.
362+
},
363+
}
364+
365+
for _, tt := range tests {
366+
t.Run(tt.name, func(t *testing.T) {
367+
var sampleLayers []imagespec.Descriptor
368+
for i := 0; i < tt.layersNum; i++ {
369+
sampleLayers = append(sampleLayers, imagespec.Descriptor{
370+
MediaType: imagespec.MediaTypeImageLayerGzip,
371+
Digest: sampleDigest,
372+
})
373+
}
374+
gotS := getLayers(context.Background(), sampleKey, sampleLayers, sampleValidate)
375+
got := len(strings.Split(gotS, ","))
376+
assert.Equal(t, tt.wantNum, got)
377+
})
378+
}
379+
}

0 commit comments

Comments
 (0)