diff --git a/Makefile b/Makefile index 5a258adf05c69..4d5e2f448f2cc 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)`. @@ -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 @@ -111,25 +111,26 @@ 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)) -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 $(GO) list ${GO_TAGS} ./... | grep -v /integration) +API_PACKAGES := $(shell $(GO) -C api 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) \ + ) 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 -GOPATHS=$(shell $(GO) env GOPATH | tr ":" "\n" | tr ";" "\n") +# Replaces ":" (*nix), ";" (windows) with newline for easy parsing +GOPATHS := $(shell $(GO) env GOPATH | tr ":" "\n" | tr ";" "\n") TESTFLAGS_RACE= GO_BUILD_FLAGS ?= @@ -213,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) $@" @@ -487,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} diff --git a/internal/cri/server/container_status_test.go b/internal/cri/server/container_status_test.go index 7cca030c390b1..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" ) @@ -302,10 +303,17 @@ 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") } +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/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/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/podsandbox/controller.go b/internal/cri/server/podsandbox/controller.go index 24903d9c23fb8..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" @@ -34,7 +33,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" @@ -90,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(), } @@ -109,13 +106,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) -} - type Controller struct { // config contains all configurations. config criconfig.Config @@ -123,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/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 0d3ff768242a2..9badf42dc76bc 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" @@ -38,7 +37,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" @@ -79,15 +77,16 @@ 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) + + pauseImage, err := c.client.GetImage(ctx, sandboxImage) 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 := pauseImage.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 +134,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 +173,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) @@ -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), @@ -341,28 +340,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) (*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 - } - // 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) - } - newImage, err := c.imageService.GetImage(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 -} - 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 be4fafcc8714b..5298f8c78e8c1 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/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) diff --git a/internal/cri/server/service.go b/internal/cri/server/service.go index 78c6658115c4c..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" @@ -108,6 +109,10 @@ type ImageService interface { LocalResolve(refOrID string) (imagestore.Image, error) ImageFSPaths() map[string]string + + Config() criconfig.ImageConfig + + UpdateRuntimeSnapshotter(runtimeName string, imagePlatform images.ImagePlatform) } // criService implements CRIService. 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 } 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,