Skip to content

containerd2: add updated multi-snapshotters support patch #13881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: 3.0-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion SPECS/containerd2/containerd2.spec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Summary: Industry-standard container runtime
Name: %{upstream_name}2
Version: 2.0.0
Release: 9%{?dist}
Release: 10%{?dist}
License: ASL 2.0
Group: Tools/Container
URL: https://www.containerd.io
Expand All @@ -20,6 +20,7 @@ Patch0: CVE-2024-45338.patch
Patch1: CVE-2025-27144.patch
Patch2: CVE-2024-40635.patch
Patch3: CVE-2025-22872.patch
Patch4: multi-snapshotters-support.patch
%{?systemd_requires}

BuildRequires: golang
Expand Down Expand Up @@ -91,6 +92,9 @@ fi
%dir /opt/containerd/lib

%changelog
* Thu May 22 2025 Mitch Zhu <[email protected]> - 2.0.0-10
- Add updated multi-snapshotters-support patch

* Thu May 22 2025 Aninda Pradhan <[email protected]> - 2.0.0-9
- Patch CVE-2025-22872

Expand Down
279 changes: 279 additions & 0 deletions SPECS/containerd2/multi-snapshotters-support.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
From 5b8c263396b67acc8ea67e22d532e69e04085b35 Mon Sep 17 00:00:00 2001
From: Mitch Zhu <[email protected]>
Date: Thu, 22 May 2025 23:55:57 +0000
Subject: [PATCH] Add multi-snapshotter support

---
internal/cri/server/container_status_test.go | 2 +-
internal/cri/server/images/image_pull.go | 37 +++++++++++--------
internal/cri/server/images/image_pull_test.go | 2 +-
internal/cri/server/podsandbox/controller.go | 2 +-
internal/cri/server/podsandbox/sandbox_run.go | 30 ++++++++-------
internal/cri/server/service.go | 2 +-
internal/cri/store/image/image.go | 29 ++++++++++++---
7 files changed, 66 insertions(+), 38 deletions(-)

diff --git a/internal/cri/server/container_status_test.go b/internal/cri/server/container_status_test.go
index 05b1650..71dcc10 100644
--- a/internal/cri/server/container_status_test.go
+++ b/internal/cri/server/container_status_test.go
@@ -302,7 +302,7 @@ func (s *fakeImageService) LocalResolve(refOrID string) (imagestore.Image, error

func (s *fakeImageService) ImageFSPaths() map[string]string { return make(map[string]string) }

-func (s *fakeImageService) PullImage(context.Context, string, func(string) (string, string, error), *runtime.PodSandboxConfig, string) (string, error) {
+func (s *fakeImageService) PullImage(context.Context, string, func(string) (string, string, error), *runtime.PodSandboxConfig, string, string) (string, error) {
return "", errors.New("not implemented")
}

diff --git a/internal/cri/server/images/image_pull.go b/internal/cri/server/images/image_pull.go
index e59b88b..f9c90b7 100644
--- a/internal/cri/server/images/image_pull.go
+++ b/internal/cri/server/images/image_pull.go
@@ -96,6 +96,15 @@ import (

// PullImage pulls an image with authentication config.
func (c *GRPCCRIImageService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (_ *runtime.PullImageResponse, err error) {
+ imageRef := r.GetImage().GetImage()
+ snapshotter, err := c.snapshotterFromPodSandboxConfig(ctx, imageRef, r.SandboxConfig, r.GetImage().GetRuntimeHandler())
+ if err != nil {
+ return nil, err
+ }
+ return c.pullImage(ctx, r, snapshotter)
+}
+
+func (c *GRPCCRIImageService) pullImage(ctx context.Context, r *runtime.PullImageRequest, snapshotter string) (_ *runtime.PullImageResponse, err error) {

imageRef := r.GetImage().GetImage()

@@ -110,14 +119,14 @@ func (c *GRPCCRIImageService) PullImage(ctx context.Context, r *runtime.PullImag
return ParseAuth(hostauth, host)
}

- ref, err := c.CRIImageService.PullImage(ctx, imageRef, credentials, r.SandboxConfig, r.GetImage().GetRuntimeHandler())
+ ref, err := c.CRIImageService.PullImage(ctx, imageRef, credentials, r.SandboxConfig, r.GetImage().GetRuntimeHandler(), snapshotter)
if err != nil {
return nil, err
}
return &runtime.PullImageResponse{ImageRef: ref}, nil
}

-func (c *CRIImageService) PullImage(ctx context.Context, name string, credentials func(string) (string, string, error), sandboxConfig *runtime.PodSandboxConfig, runtimeHandler string) (_ string, err error) {
+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) {
span := tracing.SpanFromContext(ctx)
defer func() {
// TODO: add domain label for imagePulls metrics, and we may need to provide a mechanism
@@ -167,10 +176,6 @@ func (c *CRIImageService) PullImage(ctx context.Context, name string, credential
)

defer pcancel()
- snapshotter, err := c.snapshotterFromPodSandboxConfig(ctx, ref, sandboxConfig)
- if err != nil {
- return "", err
- }
log.G(ctx).Debugf("PullImage %q with snapshotter %s", ref, snapshotter)
span.SetAttributes(
tracing.Attribute("image.ref", ref),
@@ -761,17 +766,19 @@ func (rt *pullRequestReporterRoundTripper) RoundTrip(req *http.Request) (*http.R
// Once we know the runtime, try to override default snapshotter if it is set for this runtime.
// See https://github.com/containerd/containerd/issues/6657
func (c *CRIImageService) snapshotterFromPodSandboxConfig(ctx context.Context, imageRef string,
- s *runtime.PodSandboxConfig) (string, error) {
+ s *runtime.PodSandboxConfig, runtimeHandler string) (string, error) {
snapshotter := c.config.Snapshotter
- if s == nil || s.Annotations == nil {
- return snapshotter, nil
- }

- // TODO(kiashok): honor the new CRI runtime handler field added to v0.29.0
- // for image pull per runtime class support.
- runtimeHandler, ok := s.Annotations[annotations.RuntimeHandler]
- if !ok {
- return snapshotter, nil
+ if runtimeHandler == "" {
+ if s == nil || s.Annotations == nil {
+ return snapshotter, nil
+ } else {
+ ok := false
+ runtimeHandler, ok = s.Annotations[annotations.RuntimeHandler]
+ if !ok {
+ return snapshotter, nil
+ }
+ }
}

// TODO: Ensure error is returned if runtime not found?
diff --git a/internal/cri/server/images/image_pull_test.go b/internal/cri/server/images/image_pull_test.go
index bc79e35..af6a451 100644
--- a/internal/cri/server/images/image_pull_test.go
+++ b/internal/cri/server/images/image_pull_test.go
@@ -429,7 +429,7 @@ func TestSnapshotterFromPodSandboxConfig(t *testing.T) {
Platform: platforms.DefaultSpec(),
Snapshotter: runtimeSnapshotter,
}
- snapshotter, err := cri.snapshotterFromPodSandboxConfig(context.Background(), "test-image", tt.podSandboxConfig)
+ snapshotter, err := cri.snapshotterFromPodSandboxConfig(context.Background(), "test-image", tt.podSandboxConfig, "")
assert.Equal(t, tt.expectedSnapshotter, snapshotter)
if tt.expectedErr {
assert.Error(t, err)
diff --git a/internal/cri/server/podsandbox/controller.go b/internal/cri/server/podsandbox/controller.go
index a185a4c..8fd032b 100644
--- a/internal/cri/server/podsandbox/controller.go
+++ b/internal/cri/server/podsandbox/controller.go
@@ -110,7 +110,7 @@ type RuntimeService interface {
type ImageService interface {
LocalResolve(refOrID string) (imagestore.Image, error)
GetImage(id string) (imagestore.Image, error)
- PullImage(ctx context.Context, name string, creds func(string) (string, string, error), sc *runtime.PodSandboxConfig, runtimeHandler string) (string, error)
+ PullImage(ctx context.Context, name string, creds func(string) (string, string, error), sc *runtime.PodSandboxConfig, runtimeHandler string, snapshotter string) (string, error)
RuntimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string
PinnedImage(string) string
}
diff --git a/internal/cri/server/podsandbox/sandbox_run.go b/internal/cri/server/podsandbox/sandbox_run.go
index 53d949f..35e0075 100644
--- a/internal/cri/server/podsandbox/sandbox_run.go
+++ b/internal/cri/server/podsandbox/sandbox_run.go
@@ -77,23 +77,25 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll

sandboxImage := c.getSandboxImageName()
// Ensure sandbox container image snapshot.
- image, err := c.ensureImageExists(ctx, sandboxImage, config, metadata.RuntimeHandler)
+ ociRuntime, err := c.config.GetSandboxRuntime(config, metadata.RuntimeHandler)
if err != nil {
- return cin, fmt.Errorf("failed to get sandbox image %q: %w", sandboxImage, err)
+ return cin, fmt.Errorf("failed to get sandbox runtime: %w", err)
}
+ log.G(ctx).WithField("podsandboxid", id).Debugf("use OCI runtime %+v", ociRuntime)

- containerdImage, err := c.toContainerdImage(ctx, *image)
+ labels["oci_runtime_type"] = ociRuntime.Type
+
+ snapshotter := c.imageService.RuntimeSnapshotter(ctx, ociRuntime)
+
+ image, err := c.ensureImageExists(ctx, sandboxImage, config, metadata.RuntimeHandler, snapshotter)
if err != nil {
- return cin, fmt.Errorf("failed to get image from containerd %q: %w", image.ID, err)
+ return cin, fmt.Errorf("failed to get sandbox image %q: %w", sandboxImage, err)
}

- ociRuntime, err := c.config.GetSandboxRuntime(config, metadata.RuntimeHandler)
+ containerdImage, err := c.toContainerdImage(ctx, *image)
if err != nil {
- return cin, fmt.Errorf("failed to get sandbox runtime: %w", err)
+ return cin, fmt.Errorf("failed to get image from containerd %q: %w", image.ID, err)
}
- log.G(ctx).WithField("podsandboxid", id).Debugf("use OCI runtime %+v", ociRuntime)
-
- labels["oci_runtime_type"] = ociRuntime.Type

// Create sandbox container root directories.
sandboxRootDir := c.getSandboxRootDir(id)
@@ -173,7 +175,7 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll
snapshotterOpt = append(snapshotterOpt, extraSOpts...)

opts := []containerd.NewContainerOpts{
- containerd.WithSnapshotter(c.imageService.RuntimeSnapshotter(ctx, ociRuntime)),
+ containerd.WithSnapshotter(snapshotter),
customopts.WithNewSnapshot(id, containerdImage, snapshotterOpt...),
containerd.WithSpec(spec, specOpts...),
containerd.WithContainerLabels(sandboxLabels),
@@ -299,17 +301,19 @@ func (c *Controller) Create(_ctx context.Context, info sandbox.Sandbox, opts ...
return c.store.Save(podSandbox)
}

-func (c *Controller) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig, runtimeHandler string) (*imagestore.Image, error) {
+func (c *Controller) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig, runtimeHandler string, snapshotter string) (*imagestore.Image, error) {
image, err := c.imageService.LocalResolve(ref)
if err != nil && !errdefs.IsNotFound(err) {
return nil, fmt.Errorf("failed to get image %q: %w", ref, err)
}
if err == nil {
- return &image, nil
+ if _, ok := image.Snapshotters[snapshotter]; ok || len(image.Snapshotters) == 0 {
+ return &image, nil
+ }
}
// Pull image to ensure the image exists
// TODO: Cleaner interface
- imageID, err := c.imageService.PullImage(ctx, ref, nil, config, runtimeHandler)
+ imageID, err := c.imageService.PullImage(ctx, ref, nil, config, runtimeHandler, snapshotter)
if err != nil {
return nil, fmt.Errorf("failed to pull image %q: %w", ref, err)
}
diff --git a/internal/cri/server/service.go b/internal/cri/server/service.go
index 37d66f0..5d1546e 100644
--- a/internal/cri/server/service.go
+++ b/internal/cri/server/service.go
@@ -97,7 +97,7 @@ type RuntimeService interface {
type ImageService interface {
RuntimeSnapshotter(ctx context.Context, ociRuntime criconfig.Runtime) string

- PullImage(ctx context.Context, name string, credentials func(string) (string, string, error), sandboxConfig *runtime.PodSandboxConfig, runtimeHandler string) (string, error)
+ PullImage(ctx context.Context, name string, credentials func(string) (string, string, error), sandboxConfig *runtime.PodSandboxConfig, runtimeHandler string, snapshotter string) (string, error)
UpdateImage(ctx context.Context, r string) error

CheckImages(ctx context.Context) error
diff --git a/internal/cri/store/image/image.go b/internal/cri/store/image/image.go
index 5887e75..43ecf0d 100644
--- a/internal/cri/store/image/image.go
+++ b/internal/cri/store/image/image.go
@@ -20,6 +20,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "strings"
"sync"

"github.com/containerd/containerd/v2/core/content"
@@ -53,6 +54,8 @@ type Image struct {
ImageSpec imagespec.Image
// Pinned image to prevent it from garbage collection
Pinned bool
+ // Snapshotters is a map whose keys are snapshotters for which this image has a snapshot.
+ Snapshotters map[string]struct{}
}

// Getter is used to get images but does not make changes
@@ -170,6 +173,19 @@ func (s *Store) getImage(ctx context.Context, i images.Image) (*Image, error) {
return nil, fmt.Errorf("read image config from content store: %w", err)
}

+ info, err := s.provider.Info(ctx, desc.Digest)
+ if err != nil {
+ return nil, fmt.Errorf("get content store config info: %w", err)
+ }
+
+ snapshotters := make(map[string]struct{})
+ for label := range info.Labels {
+ const Prefix = "containerd.io/gc.ref.snapshot."
+ if strings.HasPrefix(label, Prefix) {
+ snapshotters[label[len(Prefix):]] = struct{}{}
+ }
+ }
+
var spec imagespec.Image
if err := json.Unmarshal(blob, &spec); err != nil {
return nil, fmt.Errorf("unmarshal image config %s: %w", blob, err)
@@ -178,12 +194,13 @@ func (s *Store) getImage(ctx context.Context, i images.Image) (*Image, error) {
pinned := i.Labels[labels.PinnedImageLabelKey] == labels.PinnedImageLabelValue

return &Image{
- ID: id,
- References: []string{i.Name},
- ChainID: chainID.String(),
- Size: size,
- ImageSpec: spec,
- Pinned: pinned,
+ ID: id,
+ References: []string{i.Name},
+ ChainID: chainID.String(),
+ Size: size,
+ ImageSpec: spec,
+ Pinned: pinned,
+ Snapshotters: snapshotters,
}, nil

}
--
2.34.1

Loading