From fee41f4995257fd1631ff39060b24d8d0dfa14d2 Mon Sep 17 00:00:00 2001 From: ChengyuZhu6 Date: Tue, 26 Aug 2025 14:51:09 +0800 Subject: [PATCH] checkpoint: support checkpoint create command Signed-off-by: ChengyuZhu6 --- cmd/nerdctl/checkpoint/checkpoint.go | 40 ++++++++++ cmd/nerdctl/checkpoint/checkpoint_create.go | 83 +++++++++++++++++++++ cmd/nerdctl/main.go | 4 + pkg/api/types/checkpoint_types.go | 27 +++++++ pkg/cmd/checkpoint/create.go | 66 ++++++++++++++++ 5 files changed, 220 insertions(+) create mode 100644 cmd/nerdctl/checkpoint/checkpoint.go create mode 100644 cmd/nerdctl/checkpoint/checkpoint_create.go create mode 100644 pkg/api/types/checkpoint_types.go create mode 100644 pkg/cmd/checkpoint/create.go diff --git a/cmd/nerdctl/checkpoint/checkpoint.go b/cmd/nerdctl/checkpoint/checkpoint.go new file mode 100644 index 00000000000..10a8c00108f --- /dev/null +++ b/cmd/nerdctl/checkpoint/checkpoint.go @@ -0,0 +1,40 @@ +/* + 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 checkpoint + +import ( + "github.com/spf13/cobra" + + "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" +) + +func Command() *cobra.Command { + cmd := &cobra.Command{ + Annotations: map[string]string{helpers.Category: helpers.Management}, + Use: "checkpoint", + Short: "Manage checkpoints.", + RunE: helpers.UnknownSubcommandAction, + SilenceUsage: true, + SilenceErrors: true, + } + + cmd.AddCommand( + CreateCommand(), + ) + + return cmd +} diff --git a/cmd/nerdctl/checkpoint/checkpoint_create.go b/cmd/nerdctl/checkpoint/checkpoint_create.go new file mode 100644 index 00000000000..49d4e575161 --- /dev/null +++ b/cmd/nerdctl/checkpoint/checkpoint_create.go @@ -0,0 +1,83 @@ +/* + 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 checkpoint + +import ( + "github.com/spf13/cobra" + + "github.com/containerd/nerdctl/v2/cmd/nerdctl/completion" + "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" + "github.com/containerd/nerdctl/v2/pkg/api/types" + "github.com/containerd/nerdctl/v2/pkg/clientutil" + "github.com/containerd/nerdctl/v2/pkg/cmd/checkpoint" +) + +func CreateCommand() *cobra.Command { + var cmd = &cobra.Command{ + Use: "create [OPTIONS] CONTAINER CHECKPOINT", + Short: "Create a checkpoint from a running container", + Args: cobra.ExactArgs(2), + RunE: createAction, + ValidArgsFunction: createShellComplete, + SilenceUsage: true, + SilenceErrors: true, + } + cmd.Flags().Bool("leave-running", false, "Leave the container running after checkpointing") + return cmd +} + +func processCreateFlags(cmd *cobra.Command) (types.CheckpointCreateOptions, error) { + globalOptions, err := helpers.ProcessRootCmdFlags(cmd) + if err != nil { + return types.CheckpointCreateOptions{}, err + } + + leaveRunning, err := cmd.Flags().GetBool("leave-running") + + if err != nil { + return types.CheckpointCreateOptions{}, err + } + + return types.CheckpointCreateOptions{ + Stdout: cmd.OutOrStdout(), + GOptions: globalOptions, + LeaveRunning: leaveRunning, + }, nil +} + +func createAction(cmd *cobra.Command, args []string) error { + createOptions, err := processCreateFlags(cmd) + if err != nil { + return err + } + client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), createOptions.GOptions.Namespace, createOptions.GOptions.Address) + if err != nil { + return err + } + defer cancel() + + err = checkpoint.Create(ctx, client, args[0], args[1], createOptions) + if err != nil { + return err + } + + return nil +} + +func createShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return completion.ImageNames(cmd) +} diff --git a/cmd/nerdctl/main.go b/cmd/nerdctl/main.go index c5abcc60a6c..51dfb26736e 100644 --- a/cmd/nerdctl/main.go +++ b/cmd/nerdctl/main.go @@ -31,6 +31,7 @@ import ( "github.com/containerd/log" "github.com/containerd/nerdctl/v2/cmd/nerdctl/builder" + "github.com/containerd/nerdctl/v2/cmd/nerdctl/checkpoint" "github.com/containerd/nerdctl/v2/cmd/nerdctl/completion" "github.com/containerd/nerdctl/v2/cmd/nerdctl/compose" "github.com/containerd/nerdctl/v2/cmd/nerdctl/container" @@ -350,6 +351,9 @@ Config file ($NERDCTL_TOML): %s // Manifest manifest.Command(), + + // Checkpoint + checkpoint.Command(), ) addApparmorCommand(rootCmd) container.AddCpCommand(rootCmd) diff --git a/pkg/api/types/checkpoint_types.go b/pkg/api/types/checkpoint_types.go new file mode 100644 index 00000000000..98ff5396735 --- /dev/null +++ b/pkg/api/types/checkpoint_types.go @@ -0,0 +1,27 @@ +/* + 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 types + +import "io" + +// CheckpointCreateOptions specifies options for `nerdctl checkpoint create`. +type CheckpointCreateOptions struct { + Stdout io.Writer + GOptions GlobalCommandOptions + // Leave the container running after checkpointing + LeaveRunning bool +} diff --git a/pkg/cmd/checkpoint/create.go b/pkg/cmd/checkpoint/create.go new file mode 100644 index 00000000000..937919162a9 --- /dev/null +++ b/pkg/cmd/checkpoint/create.go @@ -0,0 +1,66 @@ +/* + 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 checkpoint + +import ( + "context" + "fmt" + + containerd "github.com/containerd/containerd/v2/client" + + "github.com/containerd/nerdctl/v2/pkg/api/types" + "github.com/containerd/nerdctl/v2/pkg/idutil/containerwalker" +) + +func Create(ctx context.Context, client *containerd.Client, containerID string, checkpointDir string, options types.CheckpointCreateOptions) error { + var container containerd.Container + + walker := &containerwalker.ContainerWalker{ + Client: client, + OnFound: func(ctx context.Context, found containerwalker.Found) error { + if found.MatchCount > 1 { + return fmt.Errorf("multiple containers found with provided prefix: %s", found.Req) + } + container = found.Container + return nil + }, + } + + n, err := walker.Walk(ctx, containerID) + if err != nil { + return err + } else if n == 0 { + return fmt.Errorf("no such container: %s", containerID) + } + + opts := []containerd.CheckpointOpts{ + containerd.WithCheckpointRuntime, + containerd.WithCheckpointRW, + containerd.WithCheckpointTask, + } + if !options.LeaveRunning { + opts = append(opts, containerd.WithCheckpointTaskExit) + } + + checkpoint, err := container.Checkpoint(ctx, checkpointDir, opts...) + if err != nil { + return err + } + + fmt.Fprintf(options.Stdout, "Checkpoint created: %s\n", checkpoint.Name()) + return nil +}