Skip to content

Commit bc8a975

Browse files
authored
containerd2: add updated multi-snapshotters support patch (#13881)
1 parent dadecef commit bc8a975

2 files changed

Lines changed: 284 additions & 1 deletion

File tree

SPECS/containerd2/containerd2.spec

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
Summary: Industry-standard container runtime
66
Name: %{upstream_name}2
77
Version: 2.0.0
8-
Release: 10%{?dist}
8+
Release: 11%{?dist}
99
License: ASL 2.0
1010
Group: Tools/Container
1111
URL: https://www.containerd.io
@@ -21,6 +21,7 @@ Patch1: CVE-2025-27144.patch
2121
Patch2: CVE-2024-40635.patch
2222
Patch3: CVE-2025-22872.patch
2323
Patch4: CVE-2025-47291.patch
24+
Patch5: multi-snapshotters-support.patch
2425
%{?systemd_requires}
2526

2627
BuildRequires: golang
@@ -92,6 +93,9 @@ fi
9293
%dir /opt/containerd/lib
9394

9495
%changelog
96+
* Tue Jun 10 2025 Mitch Zhu <mitchzhu@microsoft.com> - 2.0.0-11
97+
- Add updated multi-snapshotters-support patch
98+
9599
* Fri May 30 2025 Durga Jagadeesh Palli <v-dpalli@microsoft.com> - 2.0.0-10
96100
- Patch CVE-2025-47291
97101

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
From 5b8c263396b67acc8ea67e22d532e69e04085b35 Mon Sep 17 00:00:00 2001
2+
From: Mitch Zhu <mitchzhu@microsoft.com>
3+
Date: Thu, 22 May 2025 23:55:57 +0000
4+
Subject: [PATCH] Add multi-snapshotter support
5+
6+
---
7+
internal/cri/server/container_status_test.go | 2 +-
8+
internal/cri/server/images/image_pull.go | 37 +++++++++++--------
9+
internal/cri/server/images/image_pull_test.go | 2 +-
10+
internal/cri/server/podsandbox/controller.go | 2 +-
11+
internal/cri/server/podsandbox/sandbox_run.go | 30 ++++++++-------
12+
internal/cri/server/service.go | 2 +-
13+
internal/cri/store/image/image.go | 29 ++++++++++++---
14+
7 files changed, 66 insertions(+), 38 deletions(-)
15+
16+
diff --git a/internal/cri/server/container_status_test.go b/internal/cri/server/container_status_test.go
17+
index 05b1650..71dcc10 100644
18+
--- a/internal/cri/server/container_status_test.go
19+
+++ b/internal/cri/server/container_status_test.go
20+
@@ -302,7 +302,7 @@ func (s *fakeImageService) LocalResolve(refOrID string) (imagestore.Image, error
21+
22+
func (s *fakeImageService) ImageFSPaths() map[string]string { return make(map[string]string) }
23+
24+
-func (s *fakeImageService) PullImage(context.Context, string, func(string) (string, string, error), *runtime.PodSandboxConfig, string) (string, error) {
25+
+func (s *fakeImageService) PullImage(context.Context, string, func(string) (string, string, error), *runtime.PodSandboxConfig, string, string) (string, error) {
26+
return "", errors.New("not implemented")
27+
}
28+
29+
diff --git a/internal/cri/server/images/image_pull.go b/internal/cri/server/images/image_pull.go
30+
index e59b88b..f9c90b7 100644
31+
--- a/internal/cri/server/images/image_pull.go
32+
+++ b/internal/cri/server/images/image_pull.go
33+
@@ -96,6 +96,15 @@ import (
34+
35+
// PullImage pulls an image with authentication config.
36+
func (c *GRPCCRIImageService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (_ *runtime.PullImageResponse, err error) {
37+
+ imageRef := r.GetImage().GetImage()
38+
+ snapshotter, err := c.snapshotterFromPodSandboxConfig(ctx, imageRef, r.SandboxConfig, r.GetImage().GetRuntimeHandler())
39+
+ if err != nil {
40+
+ return nil, err
41+
+ }
42+
+ return c.pullImage(ctx, r, snapshotter)
43+
+}
44+
+
45+
+func (c *GRPCCRIImageService) pullImage(ctx context.Context, r *runtime.PullImageRequest, snapshotter string) (_ *runtime.PullImageResponse, err error) {
46+
47+
imageRef := r.GetImage().GetImage()
48+
49+
@@ -110,14 +119,14 @@ func (c *GRPCCRIImageService) PullImage(ctx context.Context, r *runtime.PullImag
50+
return ParseAuth(hostauth, host)
51+
}
52+
53+
- ref, err := c.CRIImageService.PullImage(ctx, imageRef, credentials, r.SandboxConfig, r.GetImage().GetRuntimeHandler())
54+
+ ref, err := c.CRIImageService.PullImage(ctx, imageRef, credentials, r.SandboxConfig, r.GetImage().GetRuntimeHandler(), snapshotter)
55+
if err != nil {
56+
return nil, err
57+
}
58+
return &runtime.PullImageResponse{ImageRef: ref}, nil
59+
}
60+
61+
-func (c *CRIImageService) PullImage(ctx context.Context, name string, credentials func(string) (string, string, error), sandboxConfig *runtime.PodSandboxConfig, runtimeHandler string) (_ string, err error) {
62+
+func (c *CRIImageService) PullImage(ctx context.Context, name string, credentials func(string) (string, string, error), sandboxConfig *runtime.PodSandboxConfig, runtimeHandler string, snapshotter string) (_ string, err error) {
63+
span := tracing.SpanFromContext(ctx)
64+
defer func() {
65+
// TODO: add domain label for imagePulls metrics, and we may need to provide a mechanism
66+
@@ -167,10 +176,6 @@ func (c *CRIImageService) PullImage(ctx context.Context, name string, credential
67+
)
68+
69+
defer pcancel()
70+
- snapshotter, err := c.snapshotterFromPodSandboxConfig(ctx, ref, sandboxConfig)
71+
- if err != nil {
72+
- return "", err
73+
- }
74+
log.G(ctx).Debugf("PullImage %q with snapshotter %s", ref, snapshotter)
75+
span.SetAttributes(
76+
tracing.Attribute("image.ref", ref),
77+
@@ -761,17 +766,19 @@ func (rt *pullRequestReporterRoundTripper) RoundTrip(req *http.Request) (*http.R
78+
// Once we know the runtime, try to override default snapshotter if it is set for this runtime.
79+
// See https://github.com/containerd/containerd/issues/6657
80+
func (c *CRIImageService) snapshotterFromPodSandboxConfig(ctx context.Context, imageRef string,
81+
- s *runtime.PodSandboxConfig) (string, error) {
82+
+ s *runtime.PodSandboxConfig, runtimeHandler string) (string, error) {
83+
snapshotter := c.config.Snapshotter
84+
- if s == nil || s.Annotations == nil {
85+
- return snapshotter, nil
86+
- }
87+
88+
- // TODO(kiashok): honor the new CRI runtime handler field added to v0.29.0
89+
- // for image pull per runtime class support.
90+
- runtimeHandler, ok := s.Annotations[annotations.RuntimeHandler]
91+
- if !ok {
92+
- return snapshotter, nil
93+
+ if runtimeHandler == "" {
94+
+ if s == nil || s.Annotations == nil {
95+
+ return snapshotter, nil
96+
+ } else {
97+
+ ok := false
98+
+ runtimeHandler, ok = s.Annotations[annotations.RuntimeHandler]
99+
+ if !ok {
100+
+ return snapshotter, nil
101+
+ }
102+
+ }
103+
}
104+
105+
// TODO: Ensure error is returned if runtime not found?
106+
diff --git a/internal/cri/server/images/image_pull_test.go b/internal/cri/server/images/image_pull_test.go
107+
index bc79e35..af6a451 100644
108+
--- a/internal/cri/server/images/image_pull_test.go
109+
+++ b/internal/cri/server/images/image_pull_test.go
110+
@@ -429,7 +429,7 @@ func TestSnapshotterFromPodSandboxConfig(t *testing.T) {
111+
Platform: platforms.DefaultSpec(),
112+
Snapshotter: runtimeSnapshotter,
113+
}
114+
- snapshotter, err := cri.snapshotterFromPodSandboxConfig(context.Background(), "test-image", tt.podSandboxConfig)
115+
+ snapshotter, err := cri.snapshotterFromPodSandboxConfig(context.Background(), "test-image", tt.podSandboxConfig, "")
116+
assert.Equal(t, tt.expectedSnapshotter, snapshotter)
117+
if tt.expectedErr {
118+
assert.Error(t, err)
119+
diff --git a/internal/cri/server/podsandbox/controller.go b/internal/cri/server/podsandbox/controller.go
120+
index a185a4c..8fd032b 100644
121+
--- a/internal/cri/server/podsandbox/controller.go
122+
+++ b/internal/cri/server/podsandbox/controller.go
123+
@@ -110,7 +110,7 @@ type RuntimeService interface {
124+
type ImageService interface {
125+
LocalResolve(refOrID string) (imagestore.Image, error)
126+
GetImage(id string) (imagestore.Image, error)
127+
- PullImage(ctx context.Context, name string, creds func(string) (string, string, error), sc *runtime.PodSandboxConfig, runtimeHandler string) (string, error)
128+
+ PullImage(ctx context.Context, name string, creds func(string) (string, string, error), sc *runtime.PodSandboxConfig, runtimeHandler string, snapshotter string) (string, error)
129+
RuntimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string
130+
PinnedImage(string) string
131+
}
132+
diff --git a/internal/cri/server/podsandbox/sandbox_run.go b/internal/cri/server/podsandbox/sandbox_run.go
133+
index 53d949f..35e0075 100644
134+
--- a/internal/cri/server/podsandbox/sandbox_run.go
135+
+++ b/internal/cri/server/podsandbox/sandbox_run.go
136+
@@ -77,23 +77,25 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll
137+
138+
sandboxImage := c.getSandboxImageName()
139+
// Ensure sandbox container image snapshot.
140+
- image, err := c.ensureImageExists(ctx, sandboxImage, config, metadata.RuntimeHandler)
141+
+ ociRuntime, err := c.config.GetSandboxRuntime(config, metadata.RuntimeHandler)
142+
if err != nil {
143+
- return cin, fmt.Errorf("failed to get sandbox image %q: %w", sandboxImage, err)
144+
+ return cin, fmt.Errorf("failed to get sandbox runtime: %w", err)
145+
}
146+
+ log.G(ctx).WithField("podsandboxid", id).Debugf("use OCI runtime %+v", ociRuntime)
147+
148+
- containerdImage, err := c.toContainerdImage(ctx, *image)
149+
+ labels["oci_runtime_type"] = ociRuntime.Type
150+
+
151+
+ snapshotter := c.imageService.RuntimeSnapshotter(ctx, ociRuntime)
152+
+
153+
+ image, err := c.ensureImageExists(ctx, sandboxImage, config, metadata.RuntimeHandler, snapshotter)
154+
if err != nil {
155+
- return cin, fmt.Errorf("failed to get image from containerd %q: %w", image.ID, err)
156+
+ return cin, fmt.Errorf("failed to get sandbox image %q: %w", sandboxImage, err)
157+
}
158+
159+
- ociRuntime, err := c.config.GetSandboxRuntime(config, metadata.RuntimeHandler)
160+
+ containerdImage, err := c.toContainerdImage(ctx, *image)
161+
if err != nil {
162+
- return cin, fmt.Errorf("failed to get sandbox runtime: %w", err)
163+
+ return cin, fmt.Errorf("failed to get image from containerd %q: %w", image.ID, err)
164+
}
165+
- log.G(ctx).WithField("podsandboxid", id).Debugf("use OCI runtime %+v", ociRuntime)
166+
-
167+
- labels["oci_runtime_type"] = ociRuntime.Type
168+
169+
// Create sandbox container root directories.
170+
sandboxRootDir := c.getSandboxRootDir(id)
171+
@@ -173,7 +175,7 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll
172+
snapshotterOpt = append(snapshotterOpt, extraSOpts...)
173+
174+
opts := []containerd.NewContainerOpts{
175+
- containerd.WithSnapshotter(c.imageService.RuntimeSnapshotter(ctx, ociRuntime)),
176+
+ containerd.WithSnapshotter(snapshotter),
177+
customopts.WithNewSnapshot(id, containerdImage, snapshotterOpt...),
178+
containerd.WithSpec(spec, specOpts...),
179+
containerd.WithContainerLabels(sandboxLabels),
180+
@@ -299,17 +301,19 @@ func (c *Controller) Create(_ctx context.Context, info sandbox.Sandbox, opts ...
181+
return c.store.Save(podSandbox)
182+
}
183+
184+
-func (c *Controller) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig, runtimeHandler string) (*imagestore.Image, error) {
185+
+func (c *Controller) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig, runtimeHandler string, snapshotter string) (*imagestore.Image, error) {
186+
image, err := c.imageService.LocalResolve(ref)
187+
if err != nil && !errdefs.IsNotFound(err) {
188+
return nil, fmt.Errorf("failed to get image %q: %w", ref, err)
189+
}
190+
if err == nil {
191+
- return &image, nil
192+
+ if _, ok := image.Snapshotters[snapshotter]; ok || len(image.Snapshotters) == 0 {
193+
+ return &image, nil
194+
+ }
195+
}
196+
// Pull image to ensure the image exists
197+
// TODO: Cleaner interface
198+
- imageID, err := c.imageService.PullImage(ctx, ref, nil, config, runtimeHandler)
199+
+ imageID, err := c.imageService.PullImage(ctx, ref, nil, config, runtimeHandler, snapshotter)
200+
if err != nil {
201+
return nil, fmt.Errorf("failed to pull image %q: %w", ref, err)
202+
}
203+
diff --git a/internal/cri/server/service.go b/internal/cri/server/service.go
204+
index 37d66f0..5d1546e 100644
205+
--- a/internal/cri/server/service.go
206+
+++ b/internal/cri/server/service.go
207+
@@ -97,7 +97,7 @@ type RuntimeService interface {
208+
type ImageService interface {
209+
RuntimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string
210+
211+
- PullImage(ctx context.Context, name string, credentials func(string) (string, string, error), sandboxConfig *runtime.PodSandboxConfig, runtimeHandler string) (string, error)
212+
+ PullImage(ctx context.Context, name string, credentials func(string) (string, string, error), sandboxConfig *runtime.PodSandboxConfig, runtimeHandler string, snapshotter string) (string, error)
213+
UpdateImage(ctx context.Context, r string) error
214+
215+
CheckImages(ctx context.Context) error
216+
diff --git a/internal/cri/store/image/image.go b/internal/cri/store/image/image.go
217+
index 5887e75..43ecf0d 100644
218+
--- a/internal/cri/store/image/image.go
219+
+++ b/internal/cri/store/image/image.go
220+
@@ -20,6 +20,7 @@ import (
221+
"context"
222+
"encoding/json"
223+
"fmt"
224+
+ "strings"
225+
"sync"
226+
227+
"github.com/containerd/containerd/v2/core/content"
228+
@@ -53,6 +54,8 @@ type Image struct {
229+
ImageSpec imagespec.Image
230+
// Pinned image to prevent it from garbage collection
231+
Pinned bool
232+
+ // Snapshotters is a map whose keys are snapshotters for which this image has a snapshot.
233+
+ Snapshotters map[string]struct{}
234+
}
235+
236+
// Getter is used to get images but does not make changes
237+
@@ -170,6 +173,19 @@ func (s *Store) getImage(ctx context.Context, i images.Image) (*Image, error) {
238+
return nil, fmt.Errorf("read image config from content store: %w", err)
239+
}
240+
241+
+ info, err := s.provider.Info(ctx, desc.Digest)
242+
+ if err != nil {
243+
+ return nil, fmt.Errorf("get content store config info: %w", err)
244+
+ }
245+
+
246+
+ snapshotters := make(map[string]struct{})
247+
+ for label := range info.Labels {
248+
+ const Prefix = "containerd.io/gc.ref.snapshot."
249+
+ if strings.HasPrefix(label, Prefix) {
250+
+ snapshotters[label[len(Prefix):]] = struct{}{}
251+
+ }
252+
+ }
253+
+
254+
var spec imagespec.Image
255+
if err := json.Unmarshal(blob, &spec); err != nil {
256+
return nil, fmt.Errorf("unmarshal image config %s: %w", blob, err)
257+
@@ -178,12 +194,13 @@ func (s *Store) getImage(ctx context.Context, i images.Image) (*Image, error) {
258+
pinned := i.Labels[labels.PinnedImageLabelKey] == labels.PinnedImageLabelValue
259+
260+
return &Image{
261+
- ID: id,
262+
- References: []string{i.Name},
263+
- ChainID: chainID.String(),
264+
- Size: size,
265+
- ImageSpec: spec,
266+
- Pinned: pinned,
267+
+ ID: id,
268+
+ References: []string{i.Name},
269+
+ ChainID: chainID.String(),
270+
+ Size: size,
271+
+ ImageSpec: spec,
272+
+ Pinned: pinned,
273+
+ Snapshotters: snapshotters,
274+
}, nil
275+
276+
}
277+
--
278+
2.34.1
279+

0 commit comments

Comments
 (0)