...

Source file src/github.com/opencontainers/runc/checkpoint.go

Documentation: github.com/opencontainers/runc

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  
    11  	criu "github.com/checkpoint-restore/go-criu/v5/rpc"
    12  	"github.com/opencontainers/runc/libcontainer"
    13  	"github.com/opencontainers/runc/libcontainer/userns"
    14  	"github.com/opencontainers/runtime-spec/specs-go"
    15  	"github.com/sirupsen/logrus"
    16  	"github.com/urfave/cli"
    17  	"golang.org/x/sys/unix"
    18  )
    19  
    20  var checkpointCommand = cli.Command{
    21  	Name:  "checkpoint",
    22  	Usage: "checkpoint a running container",
    23  	ArgsUsage: `<container-id>
    24  
    25  Where "<container-id>" is the name for the instance of the container to be
    26  checkpointed.`,
    27  	Description: `The checkpoint command saves the state of the container instance.`,
    28  	Flags: []cli.Flag{
    29  		cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"},
    30  		cli.StringFlag{Name: "work-path", Value: "", Usage: "path for saving work files and logs"},
    31  		cli.StringFlag{Name: "parent-path", Value: "", Usage: "path for previous criu image files in pre-dump"},
    32  		cli.BoolFlag{Name: "leave-running", Usage: "leave the process running after checkpointing"},
    33  		cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
    34  		cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"},
    35  		cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
    36  		cli.BoolFlag{Name: "lazy-pages", Usage: "use userfaultfd to lazily restore memory pages"},
    37  		cli.IntFlag{Name: "status-fd", Value: -1, Usage: "criu writes \\0 to this FD once lazy-pages is ready"},
    38  		cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"},
    39  		cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"},
    40  		cli.BoolFlag{Name: "pre-dump", Usage: "dump container's memory information only, leave the container running after this"},
    41  		cli.StringFlag{Name: "manage-cgroups-mode", Value: "", Usage: "cgroups mode: 'soft' (default), 'full' and 'strict'"},
    42  		cli.StringSliceFlag{Name: "empty-ns", Usage: "create a namespace, but don't restore its properties"},
    43  		cli.BoolFlag{Name: "auto-dedup", Usage: "enable auto deduplication of memory images"},
    44  	},
    45  	Action: func(context *cli.Context) error {
    46  		if err := checkArgs(context, 1, exactArgs); err != nil {
    47  			return err
    48  		}
    49  		// XXX: Currently this is untested with rootless containers.
    50  		if os.Geteuid() != 0 || userns.RunningInUserNS() {
    51  			logrus.Warn("runc checkpoint is untested with rootless containers")
    52  		}
    53  
    54  		container, err := getContainer(context)
    55  		if err != nil {
    56  			return err
    57  		}
    58  		status, err := container.Status()
    59  		if err != nil {
    60  			return err
    61  		}
    62  		if status == libcontainer.Created || status == libcontainer.Stopped {
    63  			fatal(fmt.Errorf("Container cannot be checkpointed in %s state", status.String()))
    64  		}
    65  		options := criuOptions(context)
    66  		if !(options.LeaveRunning || options.PreDump) {
    67  			// destroy container unless we tell CRIU to keep it
    68  			defer destroy(container)
    69  		}
    70  		// these are the mandatory criu options for a container
    71  		setPageServer(context, options)
    72  		setManageCgroupsMode(context, options)
    73  		if err := setEmptyNsMask(context, options); err != nil {
    74  			return err
    75  		}
    76  		return container.Checkpoint(options)
    77  	},
    78  }
    79  
    80  func prepareImagePaths(context *cli.Context) (string, string, error) {
    81  	imagePath := context.String("image-path")
    82  	if imagePath == "" {
    83  		imagePath = getDefaultImagePath()
    84  	}
    85  
    86  	if err := os.MkdirAll(imagePath, 0o600); err != nil {
    87  		return "", "", err
    88  	}
    89  
    90  	parentPath := context.String("parent-path")
    91  	if parentPath == "" {
    92  		return imagePath, parentPath, nil
    93  	}
    94  
    95  	if filepath.IsAbs(parentPath) {
    96  		return "", "", errors.New("--parent-path must be relative")
    97  	}
    98  
    99  	realParent := filepath.Join(imagePath, parentPath)
   100  	fi, err := os.Stat(realParent)
   101  	if err == nil && !fi.IsDir() {
   102  		err = &os.PathError{Path: realParent, Err: unix.ENOTDIR}
   103  	}
   104  
   105  	if err != nil {
   106  		return "", "", fmt.Errorf("invalid --parent-path: %w", err)
   107  	}
   108  
   109  	return imagePath, parentPath, nil
   110  }
   111  
   112  func setPageServer(context *cli.Context, options *libcontainer.CriuOpts) {
   113  	// xxx following criu opts are optional
   114  	// The dump image can be sent to a criu page server
   115  	if psOpt := context.String("page-server"); psOpt != "" {
   116  		address, port, err := net.SplitHostPort(psOpt)
   117  
   118  		if err != nil || address == "" || port == "" {
   119  			fatal(errors.New("Use --page-server ADDRESS:PORT to specify page server"))
   120  		}
   121  		portInt, err := strconv.Atoi(port)
   122  		if err != nil {
   123  			fatal(errors.New("Invalid port number"))
   124  		}
   125  		options.PageServer = libcontainer.CriuPageServerInfo{
   126  			Address: address,
   127  			Port:    int32(portInt),
   128  		}
   129  	}
   130  }
   131  
   132  func setManageCgroupsMode(context *cli.Context, options *libcontainer.CriuOpts) {
   133  	if cgOpt := context.String("manage-cgroups-mode"); cgOpt != "" {
   134  		switch cgOpt {
   135  		case "soft":
   136  			options.ManageCgroupsMode = criu.CriuCgMode_SOFT
   137  		case "full":
   138  			options.ManageCgroupsMode = criu.CriuCgMode_FULL
   139  		case "strict":
   140  			options.ManageCgroupsMode = criu.CriuCgMode_STRICT
   141  		default:
   142  			fatal(errors.New("Invalid manage cgroups mode"))
   143  		}
   144  	}
   145  }
   146  
   147  var namespaceMapping = map[specs.LinuxNamespaceType]int{
   148  	specs.NetworkNamespace: unix.CLONE_NEWNET,
   149  }
   150  
   151  func setEmptyNsMask(context *cli.Context, options *libcontainer.CriuOpts) error {
   152  	/* Runc doesn't manage network devices and their configuration */
   153  	nsmask := unix.CLONE_NEWNET
   154  
   155  	for _, ns := range context.StringSlice("empty-ns") {
   156  		f, exists := namespaceMapping[specs.LinuxNamespaceType(ns)]
   157  		if !exists {
   158  			return fmt.Errorf("namespace %q is not supported", ns)
   159  		}
   160  		nsmask |= f
   161  	}
   162  
   163  	options.EmptyNs = uint32(nsmask)
   164  	return nil
   165  }
   166  

View as plain text