From 13e791ef8e9a87bdc8ed027279efa96453a0eda3 Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Mon, 2 Feb 2026 19:21:11 -0800 Subject: [PATCH 01/11] Remove GetImage dependency from Controller Signed-off-by: Maksym Pavlenko --- internal/cri/server/podsandbox/controller.go | 1 - internal/cri/server/podsandbox/sandbox_run.go | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/cri/server/podsandbox/controller.go b/internal/cri/server/podsandbox/controller.go index 24903d9c23fb8..01ce11dd76d2d 100644 --- a/internal/cri/server/podsandbox/controller.go +++ b/internal/cri/server/podsandbox/controller.go @@ -112,7 +112,6 @@ func init() { // ImageService specifies dependencies to CRI image service. 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) } diff --git a/internal/cri/server/podsandbox/sandbox_run.go b/internal/cri/server/podsandbox/sandbox_run.go index b067feb1fffda..3df998c4f1005 100644 --- a/internal/cri/server/podsandbox/sandbox_run.go +++ b/internal/cri/server/podsandbox/sandbox_run.go @@ -340,7 +340,9 @@ func (c *Controller) ensureImageExists(ctx context.Context, ref string, config * if err != nil { return nil, fmt.Errorf("failed to pull image %q: %w", ref, err) } - newImage, err := c.imageService.GetImage(imageID) + // Use LocalResolve to get the image from cache after pulling. + // After a successful pull, the image should be in the imageStore cache. + newImage, err := c.imageService.LocalResolve(imageID) if err != nil { // It's still possible that someone removed the image right after it is pulled. return nil, fmt.Errorf("failed to get image %q after pulling: %w", imageID, err) From 01a85de2c6a20795de67305b327d19ca4569602b Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Mon, 2 Feb 2026 19:28:23 -0800 Subject: [PATCH 02/11] Fetch image from containerd store instead of CRI in-memory store Signed-off-by: Maksym Pavlenko --- internal/cri/server/podsandbox/sandbox_run.go | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/cri/server/podsandbox/sandbox_run.go b/internal/cri/server/podsandbox/sandbox_run.go index 3df998c4f1005..25b2260032a7f 100644 --- a/internal/cri/server/podsandbox/sandbox_run.go +++ b/internal/cri/server/podsandbox/sandbox_run.go @@ -38,7 +38,6 @@ import ( crilabels "github.com/containerd/containerd/v2/internal/cri/labels" customopts "github.com/containerd/containerd/v2/internal/cri/opts" "github.com/containerd/containerd/v2/internal/cri/server/podsandbox/types" - imagestore "github.com/containerd/containerd/v2/internal/cri/store/image" sandboxstore "github.com/containerd/containerd/v2/internal/cri/store/sandbox" ctrdutil "github.com/containerd/containerd/v2/internal/cri/util" containerdio "github.com/containerd/containerd/v2/pkg/cio" @@ -80,14 +79,15 @@ 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) + containerdImage, err := c.ensureImageExists(ctx, sandboxImage, config, metadata.RuntimeHandler) if err != nil { return cin, fmt.Errorf("failed to get sandbox image %q: %w", sandboxImage, err) } - containerdImage, err := c.toContainerdImage(ctx, *image) + // Get the image spec from containerd image + imageSpec, err := containerdImage.Spec(ctx) 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 image spec: %w", err) } ociRuntime, err := c.config.GetSandboxRuntime(config, metadata.RuntimeHandler) @@ -135,7 +135,7 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll // NOTE: sandboxContainerSpec SHOULD NOT have side // effect, e.g. accessing/creating files, so that we can test // it safely. - spec, err := c.sandboxContainerSpec(id, config, &image.ImageSpec.Config, metadata.NetNSPath, ociRuntime.PodAnnotations) + spec, err := c.sandboxContainerSpec(id, config, &imageSpec.Config, metadata.NetNSPath, ociRuntime.PodAnnotations) if err != nil { return cin, fmt.Errorf("failed to generate sandbox container spec: %w", err) } @@ -174,12 +174,12 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll } // Generate spec options that will be applied to the spec later. - specOpts, err := c.sandboxContainerSpecOpts(config, &image.ImageSpec.Config) + specOpts, err := c.sandboxContainerSpecOpts(config, &imageSpec.Config) if err != nil { return cin, fmt.Errorf("failed to generate sandbox container spec options: %w", err) } - sandboxLabels := ctrdutil.BuildLabels(config.Labels, image.ImageSpec.Config.Labels, crilabels.ContainerKindSandbox) + sandboxLabels := ctrdutil.BuildLabels(config.Labels, imageSpec.Config.Labels, crilabels.ContainerKindSandbox) snapshotterOpt := []snapshots.Opt{snapshots.WithLabels(snapshots.FilterInheritedLabels(config.Annotations))} extraSOpts, err := sandboxSnapshotterOpts(config) @@ -326,28 +326,28 @@ 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) (containerd.Image, error) { + // Try to get the image from local store first 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 + // Image exists in cache, get it from containerd + return c.toContainerdImage(ctx, image) } // Pull image to ensure the image exists - // TODO: Cleaner interface imageID, err := c.imageService.PullImage(ctx, ref, nil, config, runtimeHandler) if err != nil { return nil, fmt.Errorf("failed to pull image %q: %w", ref, err) } - // Use LocalResolve to get the image from cache after pulling. - // After a successful pull, the image should be in the imageStore cache. - newImage, err := c.imageService.LocalResolve(imageID) + // Get the image directly from containerd using the imageID + img, err := c.client.GetImage(ctx, imageID) if err != nil { // It's still possible that someone removed the image right after it is pulled. return nil, fmt.Errorf("failed to get image %q after pulling: %w", imageID, err) } - return &newImage, nil + return img, nil } func (c *Controller) getSandboxImageName() string { From dc897c5b285d8cb7bb2f9c68f255d3949129674e Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Tue, 3 Feb 2026 10:04:19 -0800 Subject: [PATCH 03/11] Remove LocalResolve dependency from Controller Signed-off-by: Maksym Pavlenko --- internal/cri/server/images/image_pull.go | 21 ++++++++++++------- internal/cri/server/podsandbox/controller.go | 2 -- internal/cri/server/podsandbox/helpers.go | 10 --------- internal/cri/server/podsandbox/sandbox_run.go | 15 +++++++------ 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/internal/cri/server/images/image_pull.go b/internal/cri/server/images/image_pull.go index d94b47be73501..5889f6f166055 100644 --- a/internal/cri/server/images/image_pull.go +++ b/internal/cri/server/images/image_pull.go @@ -299,16 +299,23 @@ func (c *CRIImageService) pullImageWithTransferService( transferProgressReporter := newTransferProgressReporter(ref, rcancel, imagePullProgressTimeout) // Set image store opts - var sopts []transferimage.StoreOpt - sopts = append(sopts, transferimage.WithPlatforms(platforms.DefaultSpec())) - sopts = append(sopts, transferimage.WithUnpack(platforms.DefaultSpec(), snapshotter)) - sopts = append(sopts, transferimage.WithImageLabels(labels)) + sopts := []transferimage.StoreOpt{ + transferimage.WithPlatforms(platforms.DefaultSpec()), + transferimage.WithUnpack(platforms.DefaultSpec(), snapshotter), + transferimage.WithImageLabels(labels), + } + is := transferimage.NewStore(ref, sopts...) + log.G(ctx).Debugf("Getting new CRI credentials") + ch := newCRICredentials(ref, credentials) - opts := []registry.Opt{registry.WithCredentials(ch)} - opts = append(opts, registry.WithHeaders(c.config.Registry.Headers)) - opts = append(opts, registry.WithHostDir(c.config.Registry.ConfigPath)) + opts := []registry.Opt{ + registry.WithCredentials(ch), + registry.WithHeaders(c.config.Registry.Headers), + registry.WithHostDir(c.config.Registry.ConfigPath), + } + reg, err := registry.NewOCIRegistry(ctx, ref, opts...) if err != nil { return nil, fmt.Errorf("failed to create OCI registry: %w", err) diff --git a/internal/cri/server/podsandbox/controller.go b/internal/cri/server/podsandbox/controller.go index 01ce11dd76d2d..b909dd9f7f5a9 100644 --- a/internal/cri/server/podsandbox/controller.go +++ b/internal/cri/server/podsandbox/controller.go @@ -34,7 +34,6 @@ import ( "github.com/containerd/containerd/v2/internal/cri/constants" "github.com/containerd/containerd/v2/internal/cri/server/events" "github.com/containerd/containerd/v2/internal/cri/server/podsandbox/types" - imagestore "github.com/containerd/containerd/v2/internal/cri/store/image" ctrdutil "github.com/containerd/containerd/v2/internal/cri/util" osinterface "github.com/containerd/containerd/v2/pkg/os" "github.com/containerd/containerd/v2/pkg/protobuf" @@ -111,7 +110,6 @@ func init() { // ImageService specifies dependencies to CRI image service. type ImageService interface { - LocalResolve(refOrID string) (imagestore.Image, error) PullImage(ctx context.Context, name string, creds func(string) (string, string, error), sc *runtime.PodSandboxConfig, runtimeHandler string) (string, error) } diff --git a/internal/cri/server/podsandbox/helpers.go b/internal/cri/server/podsandbox/helpers.go index e0109a195737b..94268441aa39b 100644 --- a/internal/cri/server/podsandbox/helpers.go +++ b/internal/cri/server/podsandbox/helpers.go @@ -28,7 +28,6 @@ import ( containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/core/containers" crilabels "github.com/containerd/containerd/v2/internal/cri/labels" - imagestore "github.com/containerd/containerd/v2/internal/cri/store/image" sandboxstore "github.com/containerd/containerd/v2/internal/cri/store/sandbox" ctrdutil "github.com/containerd/containerd/v2/internal/cri/util" "github.com/containerd/containerd/v2/pkg/oci" @@ -73,15 +72,6 @@ func (c *Controller) getVolatileSandboxRootDir(id string) string { return filepath.Join(c.config.StateDir, sandboxesDir, id) } -// toContainerdImage converts an image object in image store to containerd image handler. -func (c *Controller) toContainerdImage(ctx context.Context, image imagestore.Image) (containerd.Image, error) { - // image should always have at least one reference. - if len(image.References) == 0 { - return nil, fmt.Errorf("invalid image with no reference %q", image.ID) - } - return c.client.GetImage(ctx, image.References[0]) -} - // runtimeSpec returns a default runtime spec used in cri-containerd. func (c *Controller) runtimeSpec(id string, opts ...oci.SpecOpts) (*runtimespec.Spec, error) { // GenerateSpec needs namespace. diff --git a/internal/cri/server/podsandbox/sandbox_run.go b/internal/cri/server/podsandbox/sandbox_run.go index 25b2260032a7f..41b6c9927ef1d 100644 --- a/internal/cri/server/podsandbox/sandbox_run.go +++ b/internal/cri/server/podsandbox/sandbox_run.go @@ -327,22 +327,21 @@ func (c *Controller) Create(_ctx context.Context, info sandbox.Sandbox, opts ... } func (c *Controller) ensureImageExists(ctx context.Context, ref string, config *runtime.PodSandboxConfig, runtimeHandler string) (containerd.Image, error) { - // Try to get the image from local store first - image, err := c.imageService.LocalResolve(ref) - if err != nil && !errdefs.IsNotFound(err) { - return nil, fmt.Errorf("failed to get image %q: %w", ref, err) - } + img, err := c.client.GetImage(ctx, ref) if err == nil { - // Image exists in cache, get it from containerd - return c.toContainerdImage(ctx, image) + return img, nil + } else if !errdefs.IsNotFound(err) { + return nil, fmt.Errorf("failed to get image %q: %w", ref, err) } + // Pull image to ensure the image exists imageID, err := c.imageService.PullImage(ctx, ref, nil, config, runtimeHandler) if err != nil { return nil, fmt.Errorf("failed to pull image %q: %w", ref, err) } + // Get the image directly from containerd using the imageID - img, err := c.client.GetImage(ctx, imageID) + img, err = c.client.GetImage(ctx, imageID) if err != nil { // It's still possible that someone removed the image right after it is pulled. return nil, fmt.Errorf("failed to get image %q after pulling: %w", imageID, err) From 842528d86f5c7ff9554686e2313332b66cce008a Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Wed, 4 Feb 2026 11:14:14 -0800 Subject: [PATCH 04/11] Move pause container pulling to CRI Signed-off-by: Maksym Pavlenko --- internal/cri/server/container_status_test.go | 4 ++ internal/cri/server/podsandbox/controller.go | 9 ----- internal/cri/server/podsandbox/sandbox_run.go | 32 ++------------- internal/cri/server/sandbox_run.go | 39 +++++++++++++++++++ internal/cri/server/service.go | 2 + 5 files changed, 49 insertions(+), 37 deletions(-) diff --git a/internal/cri/server/container_status_test.go b/internal/cri/server/container_status_test.go index 7cca030c390b1..8c5fc7f9ea9cf 100644 --- a/internal/cri/server/container_status_test.go +++ b/internal/cri/server/container_status_test.go @@ -302,6 +302,10 @@ func (s *fakeImageService) LocalResolve(refOrID string) (imagestore.Image, error func (s *fakeImageService) ImageFSPaths() map[string]string { return make(map[string]string) } +func (s *fakeImageService) Config() criconfig.ImageConfig { + return criconfig.ImageConfig{} +} + func (s *fakeImageService) PullImage(context.Context, string, func(string) (string, string, error), *runtime.PodSandboxConfig, string) (string, error) { return "", errors.New("not implemented") } diff --git a/internal/cri/server/podsandbox/controller.go b/internal/cri/server/podsandbox/controller.go index b909dd9f7f5a9..93471358bc037 100644 --- a/internal/cri/server/podsandbox/controller.go +++ b/internal/cri/server/podsandbox/controller.go @@ -25,7 +25,6 @@ import ( "github.com/containerd/plugin" "github.com/containerd/plugin/registry" imagespec "github.com/opencontainers/image-spec/specs-go/v1" - runtime "k8s.io/cri-api/pkg/apis/runtime/v1" eventtypes "github.com/containerd/containerd/api/events" containerd "github.com/containerd/containerd/v2/client" @@ -89,7 +88,6 @@ func init() { config: criRuntimePlugin.(interface{ Config() criconfig.Config }).Config(), imageConfig: criImagePlugin.(interface{ Config() criconfig.ImageConfig }).Config(), os: osinterface.RealOS{}, - imageService: criImagePlugin.(ImageService), warningService: warningPlugin.(warning.Service), store: NewStore(), } @@ -108,11 +106,6 @@ func init() { }) } -// ImageService specifies dependencies to CRI image service. -type ImageService interface { - PullImage(ctx context.Context, name string, creds func(string) (string, string, error), sc *runtime.PodSandboxConfig, runtimeHandler string) (string, error) -} - type Controller struct { // config contains all configurations. config criconfig.Config @@ -120,8 +113,6 @@ type Controller struct { imageConfig criconfig.ImageConfig // client is an instance of the containerd client client *containerd.Client - // imageService is a dependency to CRI image service. - imageService ImageService // warningService is used to emit deprecation warnings. warningService warning.Service // os is an interface for all required os operations. diff --git a/internal/cri/server/podsandbox/sandbox_run.go b/internal/cri/server/podsandbox/sandbox_run.go index 41b6c9927ef1d..01a5e3650b507 100644 --- a/internal/cri/server/podsandbox/sandbox_run.go +++ b/internal/cri/server/podsandbox/sandbox_run.go @@ -29,7 +29,6 @@ import ( "github.com/containerd/typeurl/v2" "github.com/davecgh/go-spew/spew" "github.com/opencontainers/selinux/go-selinux" - runtime "k8s.io/cri-api/pkg/apis/runtime/v1" containerd "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/core/sandbox" @@ -78,14 +77,14 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll ) sandboxImage := c.getSandboxImageName() - // Ensure sandbox container image snapshot. - containerdImage, err := c.ensureImageExists(ctx, sandboxImage, config, metadata.RuntimeHandler) + + pauseImage, err := c.client.GetImage(ctx, sandboxImage) if err != nil { return cin, fmt.Errorf("failed to get sandbox image %q: %w", sandboxImage, err) } // Get the image spec from containerd image - imageSpec, err := containerdImage.Spec(ctx) + imageSpec, err := pauseImage.Spec(ctx) if err != nil { return cin, fmt.Errorf("failed to get image spec: %w", err) } @@ -195,7 +194,7 @@ func (c *Controller) Start(ctx context.Context, id string) (cin sandbox.Controll opts := []containerd.NewContainerOpts{ containerd.WithSnapshotter(sandboxSnapshotter), - customopts.WithNewSnapshot(id, containerdImage, snapshotterOpt...), + customopts.WithNewSnapshot(id, pauseImage, snapshotterOpt...), containerd.WithSpec(spec, specOpts...), containerd.WithContainerLabels(sandboxLabels), containerd.WithContainerExtension(crilabels.SandboxMetadataExtension, &metadata), @@ -326,29 +325,6 @@ 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) (containerd.Image, error) { - img, err := c.client.GetImage(ctx, ref) - if err == nil { - return img, nil - } else if !errdefs.IsNotFound(err) { - return nil, fmt.Errorf("failed to get image %q: %w", ref, err) - } - - // Pull image to ensure the image exists - imageID, err := c.imageService.PullImage(ctx, ref, nil, config, runtimeHandler) - if err != nil { - return nil, fmt.Errorf("failed to pull image %q: %w", ref, err) - } - - // Get the image directly from containerd using the imageID - img, err = c.client.GetImage(ctx, imageID) - if err != nil { - // It's still possible that someone removed the image right after it is pulled. - return nil, fmt.Errorf("failed to get image %q after pulling: %w", imageID, err) - } - return img, nil -} - func (c *Controller) getSandboxImageName() string { // returns the name of the sandbox image used to scope pod shared resources used by the pod's containers, // if empty return the default sandbox image. diff --git a/internal/cri/server/sandbox_run.go b/internal/cri/server/sandbox_run.go index 7d32383093e06..99b9f346accbc 100644 --- a/internal/cri/server/sandbox_run.go +++ b/internal/cri/server/sandbox_run.go @@ -26,6 +26,7 @@ import ( "strings" "time" + "github.com/containerd/errdefs" "github.com/containerd/go-cni" "github.com/containerd/log" "github.com/containerd/typeurl/v2" @@ -281,6 +282,20 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox return nil, fmt.Errorf("failed to create sandbox %q: %w", id, err) } + // HACK: Ensure pause container image is present before starting the sandbox. + // Ideally, this should be called from the sandbox implementation itself, but it's + // challenging to decouple CRI image APIs and controller, as a lot of information + // needs to be pulled from various sources in order to make pull backward compatible + // with previous implementations. Additionally, the Image Service relies on an + // in-memory image store to store image metadata, making it challenging to pull images + // just via containerd client. Since most runtime implementations rely on pause + // containers anyway, the CRI layer will pre-pull the pause container to guarantee + // it exists (even though it's counter to the purpose of the sandbox API). This may + // be removed/deprecated in the distant future, if we decide to remove pause containers. + if err := c.ensurePauseImageExists(ctx, r.GetConfig(), r.GetRuntimeHandler()); err != nil { + return nil, err + } + ctrl, err := c.sandboxService.StartSandbox(ctx, sandbox.Sandboxer, id) if err != nil { var cerr podsandbox.CleanupErr @@ -379,6 +394,30 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil } +func (c *criService) ensurePauseImageExists(ctx context.Context, config *runtime.PodSandboxConfig, runtimeHandler string) error { + imageConfig := c.ImageService.Config() + + ref := criconfig.DefaultSandboxImage + + if img, ok := imageConfig.PinnedImages["sandbox"]; ok && img != "" { + ref = img + } + + _, err := c.ImageService.LocalResolve(ref) + if err == nil { + return nil + } else if !errdefs.IsNotFound(err) { + return fmt.Errorf("failed to get image %q: %w", ref, err) + } + + _, err = c.ImageService.PullImage(ctx, ref, nil, config, runtimeHandler) + if err != nil { + return fmt.Errorf("failed to pull image %q: %w", ref, err) + } + + return nil +} + // getNetworkPlugin returns the network plugin to be used by the runtime class // defaults to the global CNI options in the CRI config func (c *criService) getNetworkPlugin(runtimeClass string) cni.CNI { diff --git a/internal/cri/server/service.go b/internal/cri/server/service.go index 78c6658115c4c..fbeaa6c39575f 100644 --- a/internal/cri/server/service.go +++ b/internal/cri/server/service.go @@ -108,6 +108,8 @@ type ImageService interface { LocalResolve(refOrID string) (imagestore.Image, error) ImageFSPaths() map[string]string + + Config() criconfig.ImageConfig } // criService implements CRIService. From 151f82e57cd3ce4d0719ace3c806952690dbea4e Mon Sep 17 00:00:00 2001 From: Maksym Pavlenko Date: Wed, 4 Feb 2026 11:20:53 -0800 Subject: [PATCH 05/11] Fix ambiguous selector c.Config Signed-off-by: Maksym Pavlenko --- internal/cri/server/service_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cri/server/service_linux.go b/internal/cri/server/service_linux.go index 8bf9e502cbce5..1398a7dd889a6 100644 --- a/internal/cri/server/service_linux.go +++ b/internal/cri/server/service_linux.go @@ -67,7 +67,7 @@ func (c *criService) initPlatform() (err error) { networkAttachCount := 2 - if c.Config().UseInternalLoopback { + if c.config.UseInternalLoopback { networkAttachCount = 1 } From 7ffccac5cc3a06488a18d0aa192d699de525e025 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 18 Feb 2026 12:06:47 +0100 Subject: [PATCH 06/11] Makefile: remove trailing slash from ROOTDIR It's more common for directory-paths to not have a trailing slash; strip it so that we don't have some double slashes. Before: make protos ... + protos (cd api && buf dep update) (cd api && PATH="/go/src/github.com/containerd/containerd//bin:$PATH" buf generate) After: make protos ... + protos (cd api && buf dep update) (cd api && PATH="/go/src/github.com/containerd/containerd/bin:$PATH" buf generate) Signed-off-by: Sebastiaan van Stijn --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5a258adf05c69..29092109cc651 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ GO ?= go INSTALL ?= install # Root directory of the project (absolute path). -ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) +ROOTDIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) # Base path used to install. # The files will be installed under `$(DESTDIR)/$(PREFIX)`. From 1f0f18f92c694379fd27ab3f5446221158ec652d Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 18 Feb 2026 12:12:47 +0100 Subject: [PATCH 07/11] Makefile: remove redundant grep for vendor, integration The `go list` command is vendor-aware, and doesn't include the vendor dir; go list ./... | grep 'vendor' # (no output) For the API module, there's no need to grep for `integration` as it does not have that sub-directory. Signed-off-by: Sebastiaan van Stijn --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 29092109cc651..997fe9d190d99 100644 --- a/Makefile +++ b/Makefile @@ -111,8 +111,9 @@ GO_LDFLAGS+=' SHIM_GO_LDFLAGS=-ldflags '-X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PACKAGE) -extldflags "-static" $(EXTRA_LDFLAGS)' # Project packages. -PACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /vendor/ | grep -v /integration) -API_PACKAGES=$(shell (cd api && $(GO) list ${GO_TAGS} ./... | grep -v /vendor/ | grep -v /integration)) +PACKAGES = $(shell $(GO) list ${GO_TAGS} ./... | grep -v /integration) +API_PACKAGES = $(shell (cd api && $(GO) list ${GO_TAGS} ./...)) + TEST_REQUIRES_ROOT_PACKAGES=$(filter \ ${PACKAGES}, \ $(shell \ From 43cf58a289a9a63af5262fac5df670f7a1adeede Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 18 Feb 2026 12:25:59 +0100 Subject: [PATCH 08/11] Makefile: fix indentation Fix some mixed tabs/spaces and indentation level. Signed-off-by: Sebastiaan van Stijn --- Makefile | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 997fe9d190d99..323b022d27bcd 100644 --- a/Makefile +++ b/Makefile @@ -88,7 +88,7 @@ COMMANDS=ctr containerd containerd-stress MANPAGES=ctr.8 containerd.8 containerd-config.8 containerd-config.toml.5 ifdef BUILDTAGS - GO_BUILDTAGS = ${BUILDTAGS} + GO_BUILDTAGS = ${BUILDTAGS} endif GO_BUILDTAGS ?= GO_BUILDTAGS += urfave_cli_no_docs @@ -115,21 +115,21 @@ PACKAGES = $(shell $(GO) list ${GO_TAGS} ./... | grep -v /integration) API_PACKAGES = $(shell (cd api && $(GO) list ${GO_TAGS} ./...)) TEST_REQUIRES_ROOT_PACKAGES=$(filter \ - ${PACKAGES}, \ - $(shell \ - for f in $$(git grep -l testutil.RequiresRoot | grep -v Makefile); do \ - d="$$(dirname $$f)"; \ - [ "$$d" = "." ] && echo "${PKG}" && continue; \ - echo "${PKG}/$$d"; \ - done | sort -u) \ - ) + ${PACKAGES}, \ + $(shell \ + for f in $$(git grep -l testutil.RequiresRoot | grep -v Makefile); do \ + d="$$(dirname $$f)"; \ + [ "$$d" = "." ] && echo "${PKG}" && continue; \ + echo "${PKG}/$$d"; \ + done | sort -u) \ + ) ifdef SKIPTESTS - PACKAGES:=$(filter-out ${SKIPTESTS},${PACKAGES}) - TEST_REQUIRES_ROOT_PACKAGES:=$(filter-out ${SKIPTESTS},${TEST_REQUIRES_ROOT_PACKAGES}) + PACKAGES := $(filter-out ${SKIPTESTS},${PACKAGES}) + TEST_REQUIRES_ROOT_PACKAGES := $(filter-out ${SKIPTESTS},${TEST_REQUIRES_ROOT_PACKAGES}) endif -#Replaces ":" (*nix), ";" (windows) with newline for easy parsing +# Replaces ":" (*nix), ";" (windows) with newline for easy parsing GOPATHS=$(shell $(GO) env GOPATH | tr ":" "\n" | tr ";" "\n") TESTFLAGS_RACE= From d63c1dd1f06a8c4464fbb6385e227d89a1181f0a Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 18 Feb 2026 13:16:55 +0100 Subject: [PATCH 09/11] Makefile: use "-C" flag, and evaluate once go1.20 and up has a `-C` flag to change to a directory before running commands (see https://go.dev/cl/421436). Documentation is a bit hard to find, and doesn't mention `go mod` subcommands, but can be found in the `go build` help; go help build ... The build flags are shared by the build, clean, get, install, list, run, and test commands: -C dir Change to dir before running the command. Any files named on the command line are interpreted after changing directories. If used, this flag must be the first one in the command line. Update the Makefile to use this option where applicable, so that we can skip some `cd` and sub-shells. Also switch some assignments to use `:=` to evaluate them once. Signed-off-by: Sebastiaan van Stijn --- Makefile | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 323b022d27bcd..4d5e2f448f2cc 100644 --- a/Makefile +++ b/Makefile @@ -111,10 +111,10 @@ GO_LDFLAGS+=' SHIM_GO_LDFLAGS=-ldflags '-X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PACKAGE) -extldflags "-static" $(EXTRA_LDFLAGS)' # Project packages. -PACKAGES = $(shell $(GO) list ${GO_TAGS} ./... | grep -v /integration) -API_PACKAGES = $(shell (cd api && $(GO) list ${GO_TAGS} ./...)) +PACKAGES := $(shell $(GO) list ${GO_TAGS} ./... | grep -v /integration) +API_PACKAGES := $(shell $(GO) -C api list ${GO_TAGS} ./...) -TEST_REQUIRES_ROOT_PACKAGES=$(filter \ +TEST_REQUIRES_ROOT_PACKAGES := $(filter \ ${PACKAGES}, \ $(shell \ for f in $$(git grep -l testutil.RequiresRoot | grep -v Makefile); do \ @@ -130,7 +130,7 @@ ifdef SKIPTESTS endif # Replaces ":" (*nix), ";" (windows) with newline for easy parsing -GOPATHS=$(shell $(GO) env GOPATH | tr ":" "\n" | tr ";" "\n") +GOPATHS := $(shell $(GO) env GOPATH | tr ":" "\n" | tr ";" "\n") TESTFLAGS_RACE= GO_BUILD_FLAGS ?= @@ -214,7 +214,9 @@ root-test: ## run tests, except integration tests integration: ## run integration tests @echo "$(WHALE) $@" - @cd "${ROOTDIR}/integration/client" && $(GO) mod download && $(GOTEST) -v ${TESTFLAGS} -test.root -parallel ${TESTFLAGS_PARALLEL} . + @$(GO) -C "${ROOTDIR}/integration/client" mod download + @cd "${ROOTDIR}/integration/client" && \ + $(GOTEST) -v ${TESTFLAGS} -test.root -parallel ${TESTFLAGS_PARALLEL} . bin/cri-integration.test: @echo "$(WHALE) $@" @@ -488,16 +490,16 @@ vendor: ## ensure all the go.mod/go.sum files are up-to-date including vendor/ d @$(GO) mod tidy @$(GO) mod vendor @$(GO) mod verify - @(cd ${ROOTDIR}/api && ${GO} mod tidy) + @$(GO) -C ${ROOTDIR}/api mod tidy verify-vendor: ## verify if all the go.mod/go.sum files are up-to-date @echo "$(WHALE) $@" $(eval TMPDIR := $(shell mktemp -d)) @cp -R ${ROOTDIR} ${TMPDIR} - @(cd ${TMPDIR}/containerd && ${GO} mod tidy) - @(cd ${TMPDIR}/containerd && ${GO} mod vendor) - @(cd ${TMPDIR}/containerd && ${GO} mod verify) - @(cd ${TMPDIR}/containerd/api && ${GO} mod tidy) + @$(GO) -C ${TMPDIR}/containerd mod tidy + @$(GO) -C ${TMPDIR}/containerd mod vendor + @$(GO) -C ${TMPDIR}/containerd mod verify + @$(GO) -C ${TMPDIR}/containerd/api mod tidy @diff -r -u -q ${ROOTDIR} ${TMPDIR}/containerd @rm -rf ${TMPDIR} From ce1c42baa788317fff97a80c7ebb05d1064ce638 Mon Sep 17 00:00:00 2001 From: Michael Zappa Date: Fri, 20 Feb 2026 12:00:24 -0700 Subject: [PATCH 10/11] make linter happy in release Signed-off-by: Michael Zappa --- internal/cri/server/sandbox_stop.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/cri/server/sandbox_stop.go b/internal/cri/server/sandbox_stop.go index dacb7149a2392..4e6e131b02278 100644 --- a/internal/cri/server/sandbox_stop.go +++ b/internal/cri/server/sandbox_stop.go @@ -117,9 +117,8 @@ func (c *criService) stopPodSandbox(ctx context.Context, sandbox sandboxstore.Sa if err := c.teardownPodNetwork(ctx, sandbox); err != nil { if sandbox.CNIResult != nil { return fmt.Errorf("failed to destroy network for sandbox %q: %w", id, err) - } else { - log.G(ctx).WithError(err).Warnf("failed to destroy network for sandbox %q; and ignoring because the sandbox network setup result is nil indicating the network setup never completed", id) } + log.G(ctx).WithError(err).Warnf("failed to destroy network for sandbox %q; and ignoring because the sandbox network setup result is nil indicating the network setup never completed", id) } if err := sandbox.NetNS.Remove(); err != nil { return fmt.Errorf("failed to remove network namespace for sandbox %q: %w", id, err) From e9622481f067ef09ba590a80b30628185fb28935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabiano=20Fid=C3=AAncio?= Date: Thu, 29 Jan 2026 20:39:38 +0100 Subject: [PATCH 11/11] cri: propagate runtime-specific snapshotters to image service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When users configure a snapshotter in the runtime config (e.g., `plugins."io.containerd.cri.v1.runtime".containerd.runtimes.kata.snapshotter`), the CRI image service was not aware of this configuration. This caused images to be pulled with the default snapshotter instead of the runtime-specific one, because the image service's runtimePlatforms map was not populated with these runtime-to-snapshotter mappings. Let's make sure that during the CRI plugin init, we iterate over all the configured runtimes, and propagate any snapshotter configuration to the image service. The issue was found while working on #12835. Signed-off-by: Fabiano FidĂȘncio --- internal/cri/server/container_status_test.go | 4 ++++ internal/cri/server/images/service.go | 16 +++++++++++++++ internal/cri/server/service.go | 3 +++ plugins/cri/cri.go | 21 ++++++++++++++++++-- 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/internal/cri/server/container_status_test.go b/internal/cri/server/container_status_test.go index 8c5fc7f9ea9cf..0988d8afee21a 100644 --- a/internal/cri/server/container_status_test.go +++ b/internal/cri/server/container_status_test.go @@ -32,6 +32,7 @@ import ( "github.com/stretchr/testify/assert" runtime "k8s.io/cri-api/pkg/apis/runtime/v1" + "github.com/containerd/containerd/v2/internal/cri/server/images" containerstore "github.com/containerd/containerd/v2/internal/cri/store/container" imagestore "github.com/containerd/containerd/v2/internal/cri/store/image" ) @@ -310,6 +311,9 @@ func (s *fakeImageService) PullImage(context.Context, string, func(string) (stri return "", errors.New("not implemented") } +func (s *fakeImageService) UpdateRuntimeSnapshotter(runtimeName string, imagePlatform images.ImagePlatform) { +} + func patchExceptedWithState(expected *runtime.ContainerStatus, state runtime.ContainerState) { expected.State = state switch state { diff --git a/internal/cri/server/images/service.go b/internal/cri/server/images/service.go index 7604cfffbeb3c..9654d206ac0e1 100644 --- a/internal/cri/server/images/service.go +++ b/internal/cri/server/images/service.go @@ -139,6 +139,22 @@ func NewService(config criconfig.ImageConfig, options *CRIImageServiceOptions) ( return &svc, nil } +// UpdateRuntimeSnapshotter adds or updates the snapshotter mapping for a runtime. +// This is called by the main CRI plugin after both image and runtime plugins are initialized, +// to propagate runtime-specific snapshotters configured in the runtime plugin's config. +func (c *CRIImageService) UpdateRuntimeSnapshotter(runtimeName string, imagePlatform ImagePlatform) { + if c.runtimePlatforms == nil { + c.runtimePlatforms = make(map[string]ImagePlatform) + } + // Don't override if already configured + if _, exists := c.runtimePlatforms[runtimeName]; exists { + log.L.Debugf("Runtime %q already has snapshotter configured, not overriding", runtimeName) + return + } + c.runtimePlatforms[runtimeName] = imagePlatform + log.L.Infof("Registered runtime %q with snapshotter %q", runtimeName, imagePlatform.Snapshotter) +} + // LocalResolve resolves image reference locally and returns corresponding image metadata. It // returns errdefs.ErrNotFound if the reference doesn't exist. func (c *CRIImageService) LocalResolve(refOrID string) (imagestore.Image, error) { diff --git a/internal/cri/server/service.go b/internal/cri/server/service.go index fbeaa6c39575f..c462d46dc09da 100644 --- a/internal/cri/server/service.go +++ b/internal/cri/server/service.go @@ -42,6 +42,7 @@ import ( criconfig "github.com/containerd/containerd/v2/internal/cri/config" "github.com/containerd/containerd/v2/internal/cri/nri" "github.com/containerd/containerd/v2/internal/cri/server/events" + "github.com/containerd/containerd/v2/internal/cri/server/images" containerstore "github.com/containerd/containerd/v2/internal/cri/store/container" imagestore "github.com/containerd/containerd/v2/internal/cri/store/image" "github.com/containerd/containerd/v2/internal/cri/store/label" @@ -110,6 +111,8 @@ type ImageService interface { ImageFSPaths() map[string]string Config() criconfig.ImageConfig + + UpdateRuntimeSnapshotter(runtimeName string, imagePlatform images.ImagePlatform) } // criService implements CRIService. diff --git a/plugins/cri/cri.go b/plugins/cri/cri.go index b87be27eb4b19..82330c9903afd 100644 --- a/plugins/cri/cri.go +++ b/plugins/cri/cri.go @@ -33,6 +33,7 @@ import ( "github.com/containerd/containerd/v2/internal/cri/constants" "github.com/containerd/containerd/v2/internal/cri/instrument" "github.com/containerd/containerd/v2/internal/cri/server" + "github.com/containerd/containerd/v2/internal/cri/server/images" nriservice "github.com/containerd/containerd/v2/internal/nri" "github.com/containerd/containerd/v2/plugins" "github.com/containerd/containerd/v2/plugins/services/warning" @@ -80,6 +81,22 @@ func initCRIService(ic *plugin.InitContext) (interface{}, error) { return nil, fmt.Errorf("unable to load CRI image service plugin dependency: %w", err) } + // Propagate runtime-specific snapshotters from runtime config to image service. + // This is needed because users may configure snapshotters in the runtime config + // (containerd.runtimes..snapshotter) which the image service needs for pulling. + runtimeSvc := criRuntimePlugin.(server.RuntimeService) + imageSvc := criImagePlugin.(server.ImageService) + runtimeConfig := runtimeSvc.Config() + for runtimeName, rt := range runtimeConfig.Runtimes { + if rt.Snapshotter != "" { + imagePlatform := images.ImagePlatform{ + Snapshotter: rt.Snapshotter, + Platform: platforms.DefaultSpec(), + } + imageSvc.UpdateRuntimeSnapshotter(runtimeName, imagePlatform) + } + } + if warnings, err := criconfig.ValidateServerConfig(ic.Context, config); err != nil { return nil, fmt.Errorf("invalid cri image config: %w", err) } else if len(warnings) > 0 { @@ -115,8 +132,8 @@ func initCRIService(ic *plugin.InitContext) (interface{}, error) { } options := &server.CRIServiceOptions{ - RuntimeService: criRuntimePlugin.(server.RuntimeService), - ImageService: criImagePlugin.(server.ImageService), + RuntimeService: runtimeSvc, + ImageService: imageSvc, StreamingConfig: streamingConfig, NRI: getNRIAPI(ic), Client: client,