Skip to content

Commit f4713aa

Browse files
adiskyruiwen-zhao
authored andcommitted
Pinned image support
Signed-off-by: Aditi Sharma <[email protected]> (cherry picked from commit fe4f8bd) Signed-off-by: ruiwen-zhao <[email protected]>
1 parent 3a6c2c5 commit f4713aa

7 files changed

Lines changed: 144 additions & 13 deletions

File tree

integration/containerd_image_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,3 +206,21 @@ func TestContainerdImageInOtherNamespaces(t *testing.T) {
206206
}
207207
assert.NoError(t, Consistently(checkImage, 100*time.Millisecond, time.Second))
208208
}
209+
210+
func TestContainerdSandboxImage(t *testing.T) {
211+
var pauseImage = GetImage(Pause)
212+
ctx := context.Background()
213+
214+
t.Log("make sure the pause image exist")
215+
pauseImg, err := containerdClient.GetImage(ctx, pauseImage)
216+
require.NoError(t, err)
217+
t.Log("ensure correct labels are set on pause image")
218+
assert.Equal(t, pauseImg.Labels()["io.cri-containerd.pinned"], "pinned")
219+
220+
t.Log("pause image should be seen by cri plugin")
221+
pimg, err := imageService.ImageStatus(&runtime.ImageSpec{Image: pauseImage})
222+
require.NoError(t, err)
223+
require.NotNil(t, pimg)
224+
t.Log("verify pinned field is set for pause image")
225+
assert.True(t, pimg.Pinned)
226+
}

pkg/cri/labels/labels.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package labels
18+
19+
const (
20+
// criContainerdPrefix is common prefix for cri-containerd
21+
criContainerdPrefix = "io.cri-containerd"
22+
// ImageLabelKey is the label key indicating the image is managed by cri plugin.
23+
ImageLabelKey = criContainerdPrefix + ".image"
24+
// ImageLabelValue is the label value indicating the image is managed by cri plugin.
25+
ImageLabelValue = "managed"
26+
// PinnedImageLabelKey is the label value indicating the image is pinned.
27+
PinnedImageLabelKey = criContainerdPrefix + ".pinned"
28+
// PinnedImageLabelValue is the label value indicating the image is pinned.
29+
PinnedImageLabelValue = "pinned"
30+
)

pkg/cri/server/helpers.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,6 @@ const (
7878
containerKindSandbox = "sandbox"
7979
// containerKindContainer is a label value indicating container is application container
8080
containerKindContainer = "container"
81-
// imageLabelKey is the label key indicating the image is managed by cri plugin.
82-
imageLabelKey = criContainerdPrefix + ".image"
83-
// imageLabelValue is the label value indicating the image is managed by cri plugin.
84-
imageLabelValue = "managed"
8581
// sandboxMetadataExtension is an extension name that identify metadata of sandbox in CreateContainerRequest
8682
sandboxMetadataExtension = criContainerdPrefix + ".sandbox.metadata"
8783
// containerMetadataExtension is an extension name that identify metadata of container in CreateContainerRequest

pkg/cri/server/image_pull.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/containerd/containerd/errdefs"
3434
containerdimages "github.com/containerd/containerd/images"
3535
"github.com/containerd/containerd/log"
36+
crilabels "github.com/containerd/containerd/pkg/cri/labels"
3637
snpkg "github.com/containerd/containerd/pkg/snapshotters"
3738
distribution "github.com/containerd/containerd/reference/docker"
3839
"github.com/containerd/containerd/remotes/docker"
@@ -113,12 +114,14 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
113114
}
114115
)
115116

117+
labels := c.getLabels(ctx, ref)
118+
116119
pullOpts := []containerd.RemoteOpt{
117120
containerd.WithSchema1Conversion,
118121
containerd.WithResolver(resolver),
119122
containerd.WithPullSnapshotter(c.config.ContainerdConfig.Snapshotter),
120123
containerd.WithPullUnpack,
121-
containerd.WithPullLabel(imageLabelKey, imageLabelValue),
124+
containerd.WithPullLabels(labels),
122125
containerd.WithMaxConcurrentDownloads(c.config.MaxConcurrentDownloads),
123126
containerd.WithImageHandler(imageHandler),
124127
containerd.WithUnpackOpts([]containerd.UnpackOpt{
@@ -154,7 +157,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
154157
if r == "" {
155158
continue
156159
}
157-
if err := c.createImageReference(ctx, r, image.Target()); err != nil {
160+
if err := c.createImageReference(ctx, r, image.Target(), labels); err != nil {
158161
return nil, fmt.Errorf("failed to create image reference %q: %w", r, err)
159162
}
160163
// Update image store to reflect the newest state in containerd.
@@ -219,26 +222,44 @@ func ParseAuth(auth *runtime.AuthConfig, host string) (string, string, error) {
219222
// Note that because create and update are not finished in one transaction, there could be race. E.g.
220223
// the image reference is deleted by someone else after create returns already exists, but before update
221224
// happens.
222-
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor) error {
225+
func (c *criService) createImageReference(ctx context.Context, name string, desc imagespec.Descriptor, labels map[string]string) error {
223226
img := containerdimages.Image{
224227
Name: name,
225228
Target: desc,
226229
// Add a label to indicate that the image is managed by the cri plugin.
227-
Labels: map[string]string{imageLabelKey: imageLabelValue},
230+
Labels: labels,
228231
}
229232
// TODO(random-liu): Figure out which is the more performant sequence create then update or
230233
// update then create.
231234
oldImg, err := c.client.ImageService().Create(ctx, img)
232235
if err == nil || !errdefs.IsAlreadyExists(err) {
233236
return err
234237
}
235-
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[imageLabelKey] == imageLabelValue {
238+
if oldImg.Target.Digest == img.Target.Digest && oldImg.Labels[crilabels.ImageLabelKey] == labels[crilabels.ImageLabelKey] {
236239
return nil
237240
}
238-
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+imageLabelKey)
241+
_, err = c.client.ImageService().Update(ctx, img, "target", "labels."+crilabels.ImageLabelKey)
239242
return err
240243
}
241244

245+
// getLabels get image labels to be added on CRI image
246+
func (c *criService) getLabels(ctx context.Context, name string) map[string]string {
247+
labels := map[string]string{crilabels.ImageLabelKey: crilabels.ImageLabelValue}
248+
configSandboxImage := c.config.SandboxImage
249+
// parse sandbox image
250+
sandboxNamedRef, err := distribution.ParseDockerRef(configSandboxImage)
251+
if err != nil {
252+
log.G(ctx).Errorf("failed to parse sandbox image from config %s", sandboxNamedRef)
253+
return nil
254+
}
255+
sandboxRef := sandboxNamedRef.String()
256+
// Adding pinned image label to sandbox image
257+
if sandboxRef == name {
258+
labels[crilabels.PinnedImageLabelKey] = crilabels.PinnedImageLabelValue
259+
}
260+
return labels
261+
}
262+
242263
// updateImage updates image store to reflect the newest state of an image reference
243264
// in containerd. If the reference is not managed by the cri plugin, the function also
244265
// generates necessary metadata for the image and make it managed.
@@ -247,22 +268,23 @@ func (c *criService) updateImage(ctx context.Context, r string) error {
247268
if err != nil && !errdefs.IsNotFound(err) {
248269
return fmt.Errorf("get image by reference: %w", err)
249270
}
250-
if err == nil && img.Labels()[imageLabelKey] != imageLabelValue {
271+
if err == nil && img.Labels()[crilabels.ImageLabelKey] != crilabels.ImageLabelValue {
251272
// Make sure the image has the image id as its unique
252273
// identifier that references the image in its lifetime.
253274
configDesc, err := img.Config(ctx)
254275
if err != nil {
255276
return fmt.Errorf("get image id: %w", err)
256277
}
257278
id := configDesc.Digest.String()
258-
if err := c.createImageReference(ctx, id, img.Target()); err != nil {
279+
labels := c.getLabels(ctx, id)
280+
if err := c.createImageReference(ctx, id, img.Target(), labels); err != nil {
259281
return fmt.Errorf("create image id reference %q: %w", id, err)
260282
}
261283
if err := c.imageStore.Update(ctx, id); err != nil {
262284
return fmt.Errorf("update image store for %q: %w", id, err)
263285
}
264286
// The image id is ready, add the label to mark the image as managed.
265-
if err := c.createImageReference(ctx, r, img.Target()); err != nil {
287+
if err := c.createImageReference(ctx, r, img.Target(), labels); err != nil {
266288
return fmt.Errorf("create managed label: %w", err)
267289
}
268290
}

pkg/cri/server/image_pull_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@
1717
package server
1818

1919
import (
20+
"context"
2021
"encoding/base64"
2122
"testing"
2223

2324
"github.com/stretchr/testify/assert"
2425
runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
2526

2627
criconfig "github.com/containerd/containerd/pkg/cri/config"
28+
"github.com/containerd/containerd/pkg/cri/labels"
2729
)
2830

2931
func TestParseAuth(t *testing.T) {
@@ -327,3 +329,56 @@ func TestEncryptedImagePullOpts(t *testing.T) {
327329
assert.Equal(t, test.expectedOpts, got)
328330
}
329331
}
332+
func TestImageGetLabels(t *testing.T) {
333+
334+
criService := newTestCRIService()
335+
336+
tests := []struct {
337+
name string
338+
expectedLabel map[string]string
339+
configSandboxImage string
340+
pullImageName string
341+
}{
342+
{
343+
name: "pinned image labels should get added on sandbox image",
344+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
345+
configSandboxImage: "k8s.gcr.io/pause:3.9",
346+
pullImageName: "k8s.gcr.io/pause:3.9",
347+
},
348+
{
349+
name: "pinned image labels should get added on sandbox image without tag",
350+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
351+
configSandboxImage: "k8s.gcr.io/pause",
352+
pullImageName: "k8s.gcr.io/pause:latest",
353+
},
354+
{
355+
name: "pinned image labels should get added on sandbox image specified with tag and digest both",
356+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
357+
configSandboxImage: "k8s.gcr.io/pause:3.9@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
358+
pullImageName: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
359+
},
360+
361+
{
362+
name: "pinned image labels should get added on sandbox image specified with digest",
363+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue, labels.PinnedImageLabelKey: labels.PinnedImageLabelValue},
364+
configSandboxImage: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
365+
pullImageName: "k8s.gcr.io/pause@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2",
366+
},
367+
368+
{
369+
name: "pinned image labels should not get added on other image",
370+
expectedLabel: map[string]string{labels.ImageLabelKey: labels.ImageLabelValue},
371+
configSandboxImage: "k8s.gcr.io/pause:3.9",
372+
pullImageName: "k8s.gcr.io/random:latest",
373+
},
374+
}
375+
376+
for _, tt := range tests {
377+
t.Run(tt.name, func(t *testing.T) {
378+
criService.config.SandboxImage = tt.configSandboxImage
379+
labels := criService.getLabels(context.Background(), tt.pullImageName)
380+
assert.Equal(t, tt.expectedLabel, labels)
381+
382+
})
383+
}
384+
}

pkg/cri/server/image_status.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,15 @@ func (c *criService) ImageStatus(ctx context.Context, r *runtime.ImageStatusRequ
5959
// toCRIImage converts internal image object to CRI runtime.Image.
6060
func toCRIImage(image imagestore.Image) *runtime.Image {
6161
repoTags, repoDigests := parseImageReferences(image.References)
62+
6263
runtimeImage := &runtime.Image{
6364
Id: image.ID,
6465
RepoTags: repoTags,
6566
RepoDigests: repoDigests,
6667
Size_: uint64(image.Size),
68+
Pinned: image.Pinned,
6769
}
70+
6871
uid, username := getUserFromImage(image.ImageSpec.Config.User)
6972
if uid != nil {
7073
runtimeImage.Uid = &runtime.Int64Value{Value: *uid}

pkg/cri/store/image/image.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/containerd/containerd"
2626
"github.com/containerd/containerd/content"
2727
"github.com/containerd/containerd/errdefs"
28+
"github.com/containerd/containerd/pkg/cri/labels"
2829
"github.com/containerd/containerd/pkg/cri/util"
2930

3031
imagedigest "github.com/opencontainers/go-digest"
@@ -46,6 +47,8 @@ type Image struct {
4647
Size int64
4748
// ImageSpec is the oci image structure which describes basic information about the image.
4849
ImageSpec imagespec.Image
50+
// Pinned image to prevent it from garbage collection
51+
Pinned bool
4952
}
5053

5154
// Store stores all images.
@@ -143,13 +146,17 @@ func getImage(ctx context.Context, i containerd.Image) (*Image, error) {
143146
return nil, fmt.Errorf("unmarshal image config %s: %w", rb, err)
144147
}
145148

149+
pinned := i.Labels()[labels.PinnedImageLabelKey] == labels.PinnedImageLabelValue
150+
146151
return &Image{
147152
ID: id,
148153
References: []string{i.Name()},
149154
ChainID: chainID.String(),
150155
Size: size,
151156
ImageSpec: ociimage,
157+
Pinned: pinned,
152158
}, nil
159+
153160
}
154161

155162
// Resolve resolves a image reference to image id.

0 commit comments

Comments
 (0)