diff --git a/RELEASES.md b/RELEASES.md index 5dee8118d2..d1af474243 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -601,18 +601,19 @@ against total impact. The deprecated features are shown in the following table: | Component | Deprecation release | Target release for removal | Recommendation | -|----------------------------------------------------------------------------------|---------------------|---------------------------------------|------------------------------------------| -| Runtime V1 API and implementation (`io.containerd.runtime.v1.linux`) | containerd v1.4 | containerd v2.0 ✅ | Use `io.containerd.runc.v2` | -| Runc V1 implementation of Runtime V2 (`io.containerd.runc.v1`) | containerd v1.4 | containerd v2.0 ✅ | Use `io.containerd.runc.v2` | -| Built-in `aufs` snapshotter | containerd v1.5 | containerd v2.0 ✅ | Use `overlayfs` snapshotter | -| Container label `containerd.io/restart.logpath` | containerd v1.5 | containerd v2.0 ✅ | Use `containerd.io/restart.loguri` label | -| `cri-containerd-*.tar.gz` release bundles | containerd v1.6 | containerd v2.0 ✅ | Use `containerd-*.tar.gz` bundles | -| Pulling Schema 1 images (`application/vnd.docker.distribution.manifest.v1+prettyjws`) | containerd v1.7 | containerd v2.1 (Disabled in v2.0) ✅ | Use Schema 2 or OCI images | -| CRI `v1alpha2` | containerd v1.7 | containerd v2.0 ✅ | Use CRI `v1` | -| Legacy CRI implementation of podsandbox support | containerd v2.0 | containerd v2.0 ✅ | | -| Go-Plugin library (`*.so`) as containerd runtime plugin | containerd v2.0 | containerd v2.1 ✅ | Use external plugins (proxy or binary) | -| NRI v0.1.0 plugin support | containerd v2.2 | containerd v2.3 | Use the v010-adapter NRI plugin, or update v0.1.0 plugins to use the current NRI API | -| cgroup v1 support | containerd v2.2 | (May 2029) | Use cgroup v2 | +|----------------------------------------------------------------------------------|---------------------|---------------------------------------|------------------------------------------------------------------------------------------------| +| Runtime V1 API and implementation (`io.containerd.runtime.v1.linux`) | containerd v1.4 | containerd v2.0 ✅ | Use `io.containerd.runc.v2` | +| Runc V1 implementation of Runtime V2 (`io.containerd.runc.v1`) | containerd v1.4 | containerd v2.0 ✅ | Use `io.containerd.runc.v2` | +| Built-in `aufs` snapshotter | containerd v1.5 | containerd v2.0 ✅ | Use `overlayfs` snapshotter | +| Container label `containerd.io/restart.logpath` | containerd v1.5 | containerd v2.0 ✅ | Use `containerd.io/restart.loguri` label | +| `cri-containerd-*.tar.gz` release bundles | containerd v1.6 | containerd v2.0 ✅ | Use `containerd-*.tar.gz` bundles | +| Pulling Schema 1 images (`application/vnd.docker.distribution.manifest.v1+prettyjws`) | containerd v1.7 | containerd v2.1 (Disabled in v2.0) ✅ | Use Schema 2 or OCI images | +| CRI `v1alpha2` | containerd v1.7 | containerd v2.0 ✅ | Use CRI `v1` | +| Legacy CRI implementation of podsandbox support | containerd v2.0 | containerd v2.0 ✅ | | +| Go-Plugin library (`*.so`) as containerd runtime plugin | containerd v2.0 | containerd v2.1 ✅ | Use external plugins (proxy or binary) | +| NRI v0.1.0 plugin support | containerd v2.2 | containerd v2.3 | Use the v010-adapter NRI plugin, or update v0.1.0 plugins to use the current NRI API | +| cgroup v1 support | containerd v2.2 | (May 2029) | Use cgroup v2 | +| Restoring checkpoint data during CRI `CreateContainer` | containerd v2.3 | containerd v2.4 | Follow [KEP-5823](https://github.com/kubernetes/enhancements/issues/5823) for a replacement `RestorePod` API | - Pulling Schema 1 images has been disabled in containerd v2.0, but it still can be enabled by setting an environment variable `CONTAINERD_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE=1` until containerd v2.1. `ctr` users have to specify `--local` too (e.g., `ctr images pull --local`). Users of CRI clients (such as Kubernetes and `crictl`) have to specify this environment variable on the containerd daemon (usually in the systemd unit). diff --git a/contrib/checkpoint/checkpoint-restore-cri-test.sh b/contrib/checkpoint/checkpoint-restore-cri-test.sh index 6f76f558ac..427e5bd7a7 100755 --- a/contrib/checkpoint/checkpoint-restore-cri-test.sh +++ b/contrib/checkpoint/checkpoint-restore-cri-test.sh @@ -126,6 +126,12 @@ function test_from_archive() { echo "error: CDI annotation was not filtered or safe annotation missing: $actual_annots" exit 1 fi + echo "--> Verifying deprecation warning via API (archive): " + archive_ts=$(../../bin/ctr deprecations list --format=json | jq -r '.[] | select(.id == "io.containerd.deprecation/cri-create-container-checkpoint-restore") | .lastOccurrence') + if [ -z "$archive_ts" ] || [ "$archive_ts" = "null" ]; then + echo "error: CRICreateContainerCheckpointRestore deprecation warning not found in API introspection (archive)" + exit 1 + fi # Cleanup echo "--> Cleanup images: " (crictl rmi "${TEST_IMAGE}" || true) | sed 's/^/----> \t/' @@ -198,6 +204,16 @@ function test_from_oci() { rm -f "$RESTORE_JSON" "$RESTORE_POD_JSON" echo -n "--> Start container from checkpoint: " crictl start "$ctr_id" + echo "--> Verifying deprecation warning via API (oci): " + oci_ts=$(../../bin/ctr deprecations list --format=json | jq -r '.[] | select(.id == "io.containerd.deprecation/cri-create-container-checkpoint-restore") | .lastOccurrence') + if [ -z "$oci_ts" ] || [ "$oci_ts" = "null" ]; then + echo "error: CRICreateContainerCheckpointRestore deprecation warning not found in API introspection (oci)" + exit 1 + fi + if [ "$archive_ts" = "$oci_ts" ]; then + echo "error: expected lastOccurrence to update after OCI restore (was $archive_ts, now $oci_ts)" + exit 1 + fi # Cleanup echo "--> Cleanup images: " ../../bin/ctr -n k8s.io images rm localhost/checkpoint-image:latest | sed 's/^/----> \t/' diff --git a/internal/cri/server/container_checkpoint_warning_test.go b/internal/cri/server/container_checkpoint_warning_test.go new file mode 100644 index 0000000000..88e9665c2a --- /dev/null +++ b/internal/cri/server/container_checkpoint_warning_test.go @@ -0,0 +1,91 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package server + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + runtime "k8s.io/cri-api/pkg/apis/runtime/v1" + + "github.com/containerd/containerd/v2/core/sandbox" + sandboxstore "github.com/containerd/containerd/v2/internal/cri/store/sandbox" + "github.com/containerd/containerd/v2/pkg/deprecation" + "github.com/containerd/containerd/v2/plugins/services/warning" +) + +type mockWarningService struct { + emitted []deprecation.Warning +} + +func (m *mockWarningService) Emit(ctx context.Context, w deprecation.Warning) { + m.emitted = append(m.emitted, w) +} + +func (m *mockWarningService) Warnings() []warning.Warning { + return nil +} + +type testSandboxService struct { + fakeSandboxService +} + +func (t *testSandboxService) SandboxStatus(ctx context.Context, sandboxer string, sandboxID string, verbose bool) (sandbox.ControllerStatus, error) { + return sandbox.ControllerStatus{ + SandboxID: sandboxID, + Pid: 1234, + State: "READY", + }, nil +} + +func TestCreateContainerCheckpointWarning(t *testing.T) { + c := newTestCRIService() + mockWarn := &mockWarningService{} + c.warningService = mockWarn + c.sandboxService = &testSandboxService{} + + sb := sandboxstore.NewSandbox( + sandboxstore.Metadata{ + ID: "test-sandbox", + Name: "test-sandbox", + Config: &runtime.PodSandboxConfig{ + Metadata: &runtime.PodSandboxMetadata{Name: "test-sandbox", Namespace: "default"}, + }, + }, + sandboxstore.Status{ + State: sandboxstore.StateReady, + }, + ) + require.NoError(t, c.sandboxStore.Add(sb)) + + // In newTestCRIService(), c.os is a FakeOS where Stat returns (nil, nil) (no error), + // causing checkpointImage to evaluate as true when checked in CreateContainer. + _, _ = c.CreateContainer(context.Background(), &runtime.CreateContainerRequest{ + PodSandboxId: "test-sandbox", + Config: &runtime.ContainerConfig{ + Metadata: &runtime.ContainerMetadata{Name: "test-container"}, + Image: &runtime.ImageSpec{Image: "/path/to/checkpoint.tar"}, + }, + SandboxConfig: &runtime.PodSandboxConfig{ + Metadata: &runtime.PodSandboxMetadata{Name: "test-sandbox", Namespace: "default"}, + }, + }) + + assert.Contains(t, mockWarn.emitted, deprecation.CRICreateContainerCheckpointRestore, "expected CRICreateContainerCheckpointRestore deprecation warning to be emitted") +} diff --git a/internal/cri/server/container_create.go b/internal/cri/server/container_create.go index 3f1096ede3..376628afe9 100644 --- a/internal/cri/server/container_create.go +++ b/internal/cri/server/container_create.go @@ -47,6 +47,7 @@ import ( "github.com/containerd/containerd/v2/internal/cri/util" "github.com/containerd/containerd/v2/internal/registrar" "github.com/containerd/containerd/v2/pkg/blockio" + "github.com/containerd/containerd/v2/pkg/deprecation" "github.com/containerd/containerd/v2/pkg/oci" "github.com/containerd/containerd/v2/pkg/tracing" ) @@ -153,6 +154,17 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta // This might be a checkpoint image. Let's pass // it to the checkpoint code. + if c.warningService != nil { + c.warningService.Emit(ctx, deprecation.CRICreateContainerCheckpointRestore) + if msg, ok := deprecation.Message(deprecation.CRICreateContainerCheckpointRestore); ok { + log.G(ctx).WithFields(log.Fields{ + "podsandboxid": sandboxID, + "containerid": id, + "containername": name, + }).Warn(msg) + } + } + if sandboxConfig.GetMetadata() == nil { return nil, fmt.Errorf("sandboxConfig must not be empty") } diff --git a/internal/cri/server/service.go b/internal/cri/server/service.go index 57e0dbcb4b..12aa6afaa6 100644 --- a/internal/cri/server/service.go +++ b/internal/cri/server/service.go @@ -57,6 +57,7 @@ import ( "github.com/containerd/containerd/v2/pkg/oci" osinterface "github.com/containerd/containerd/v2/pkg/os" "github.com/containerd/containerd/v2/plugins" + "github.com/containerd/containerd/v2/plugins/services/warning" ) var kernelSupportsRRO bool @@ -171,6 +172,8 @@ type criService struct { statsCollector *StatsCollector // shimPath is the custom PATH environment variable value from the shim manager shimPath string + // warningService is used to emit deprecation warnings. + warningService warning.Service checkCriuOnce sync.Once //nolint:nolintlint,unused // Ignore on non-Linux checkCriuErr error //nolint:nolintlint,unused // Ignore on non-Linux @@ -195,6 +198,9 @@ type CRIServiceOptions struct { // ShimPath is the custom PATH environment variable value from the shim manager ShimPath string + + // WarningService is used to emit deprecation warnings. + WarningService warning.Service } // NewCRIService returns a new instance of CRIService @@ -223,6 +229,7 @@ func NewCRIService(options *CRIServiceOptions) (CRIService, runtime.RuntimeServi runtimeHandlers: make(map[string]*runtime.RuntimeHandler), statsCollector: statsCollector, shimPath: options.ShimPath, + warningService: options.WarningService, } // TODO: Make discard time configurable diff --git a/pkg/deprecation/deprecation.go b/pkg/deprecation/deprecation.go index 45a07f4198..ef9999e61e 100644 --- a/pkg/deprecation/deprecation.go +++ b/pkg/deprecation/deprecation.go @@ -43,6 +43,8 @@ const ( RuncOptionsTaskAPIAddress Warning = Prefix + "runc-options-task-api-address" // RuncOptionsTaskAPIVersion is a warning for the use of `task_api_version` in runc options RuncOptionsTaskAPIVersion Warning = Prefix + "runc-options-task-api-version" + // CRICreateContainerCheckpointRestore is a warning for restoring checkpoint data from an image or archive during CRI CreateContainer + CRICreateContainerCheckpointRestore Warning = Prefix + "cri-create-container-checkpoint-restore" ) const ( @@ -67,8 +69,9 @@ var messages = map[Warning]string{ CgroupV1: "The support for cgroup v1 is deprecated since containerd v2.2 and will be removed by no later than May 2029. Upgrade the host to use cgroup v2.", CRIEnableCDI: "The `enable_cdi` property of `[plugins.\"io.containerd.cri.v1.runtime\"]` is deprecated, will be removed in containerd v2.3, and CDI support will always be enabled.", - RuncOptionsTaskAPIAddress: "The `task_api_address` field in runc options is deprecated since containerd v2.3. Set `task_api_address` on CreateTaskRequest instead.", - RuncOptionsTaskAPIVersion: "The `task_api_version` field in runc options is deprecated since containerd v2.3. Set `task_api_version` on CreateTaskRequest instead.", + RuncOptionsTaskAPIAddress: "The `task_api_address` field in runc options is deprecated since containerd v2.3. Set `task_api_address` on CreateTaskRequest instead.", + RuncOptionsTaskAPIVersion: "The `task_api_version` field in runc options is deprecated since containerd v2.3. Set `task_api_version` on CreateTaskRequest instead.", + CRICreateContainerCheckpointRestore: "Restoring checkpoint data from an image or archive during CRI CreateContainer is deprecated and will be removed in containerd v2.4.", } // Valid checks whether a given Warning is valid diff --git a/plugins/cri/cri.go b/plugins/cri/cri.go index 8a8fce4965..e2872d4b4f 100644 --- a/plugins/cri/cri.go +++ b/plugins/cri/cri.go @@ -99,14 +99,15 @@ func initCRIService(ic *plugin.InitContext) (any, error) { } } + ws, err := ic.GetSingle(plugins.WarningPlugin) + if err != nil { + return nil, err + } + warn := ws.(warning.Service) + 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 { - ws, err := ic.GetSingle(plugins.WarningPlugin) - if err != nil { - return nil, err - } - warn := ws.(warning.Service) for _, w := range warnings { warn.Emit(ic.Context, w) } @@ -157,6 +158,7 @@ func initCRIService(ic *plugin.InitContext) (any, error) { Client: client, SandboxControllers: sbControllers, ShimPath: shimPath, + WarningService: warn, } is := criImagePlugin.(imageService).GRPCService()