github.com/MerlinKodo/gvisor@v0.0.0-20231110090155-957f62ecf90e/runsc/cmd/checkpoint.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cmd 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "path/filepath" 22 23 "github.com/MerlinKodo/gvisor/pkg/log" 24 "github.com/MerlinKodo/gvisor/pkg/state/statefile" 25 "github.com/MerlinKodo/gvisor/runsc/cmd/util" 26 "github.com/MerlinKodo/gvisor/runsc/config" 27 "github.com/MerlinKodo/gvisor/runsc/container" 28 "github.com/MerlinKodo/gvisor/runsc/flag" 29 "github.com/MerlinKodo/gvisor/runsc/specutils" 30 "github.com/google/subcommands" 31 "golang.org/x/sys/unix" 32 ) 33 34 // File containing the container's saved image/state within the given image-path's directory. 35 const checkpointFileName = "checkpoint.img" 36 37 // Checkpoint implements subcommands.Command for the "checkpoint" command. 38 type Checkpoint struct { 39 imagePath string 40 leaveRunning bool 41 compression CheckpointCompression 42 } 43 44 // Name implements subcommands.Command.Name. 45 func (*Checkpoint) Name() string { 46 return "checkpoint" 47 } 48 49 // Synopsis implements subcommands.Command.Synopsis. 50 func (*Checkpoint) Synopsis() string { 51 return "checkpoint current state of container (experimental)" 52 } 53 54 // Usage implements subcommands.Command.Usage. 55 func (*Checkpoint) Usage() string { 56 return `checkpoint [flags] <container id> - save current state of container. 57 ` 58 } 59 60 // SetFlags implements subcommands.Command.SetFlags. 61 func (c *Checkpoint) SetFlags(f *flag.FlagSet) { 62 f.StringVar(&c.imagePath, "image-path", "", "directory path to saved container image") 63 f.BoolVar(&c.leaveRunning, "leave-running", false, "restart the container after checkpointing") 64 f.Var(newCheckpointCompressionValue(statefile.CompressionLevelFlateBestSpeed, &c.compression), "compression", "compress checkpoint image on disk. Values: none|flate-best-speed.") 65 66 // Unimplemented flags necessary for compatibility with docker. 67 var wp string 68 f.StringVar(&wp, "work-path", "", "ignored") 69 } 70 71 // Execute implements subcommands.Command.Execute. 72 func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 73 if f.NArg() != 1 { 74 f.Usage() 75 return subcommands.ExitUsageError 76 } 77 78 id := f.Arg(0) 79 conf := args[0].(*config.Config) 80 waitStatus := args[1].(*unix.WaitStatus) 81 82 cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{}) 83 if err != nil { 84 util.Fatalf("loading container: %v", err) 85 } 86 87 if c.imagePath == "" { 88 util.Fatalf("image-path flag must be provided") 89 } 90 91 if err := os.MkdirAll(c.imagePath, 0755); err != nil { 92 util.Fatalf("making directories at path provided: %v", err) 93 } 94 95 fullImagePath := filepath.Join(c.imagePath, checkpointFileName) 96 97 // Create the image file and open for writing. 98 file, err := os.OpenFile(fullImagePath, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0644) 99 if err != nil { 100 util.Fatalf("os.OpenFile(%q) failed: %v", fullImagePath, err) 101 } 102 defer file.Close() 103 104 if err := cont.Checkpoint(file, statefile.Options{Compression: c.compression.Level()}); err != nil { 105 util.Fatalf("checkpoint failed: %v", err) 106 } 107 108 if !c.leaveRunning { 109 return subcommands.ExitSuccess 110 } 111 112 // TODO(b/110843694): Make it possible to restore into same container. 113 // For now, we can fake it by destroying the container and making a 114 // new container with the same ID. This hack does not work with docker 115 // which uses the container pid to ensure that the restore-container is 116 // actually the same as the checkpoint-container. By restoring into 117 // the same container, we will solve the docker incompatibility. 118 119 // Restore into new container with same ID. 120 bundleDir := cont.BundleDir 121 if bundleDir == "" { 122 util.Fatalf("setting bundleDir") 123 } 124 125 spec, err := specutils.ReadSpec(bundleDir, conf) 126 if err != nil { 127 util.Fatalf("reading spec: %v", err) 128 } 129 130 specutils.LogSpecDebug(spec, conf.OCISeccomp) 131 132 if cont.ConsoleSocket != "" { 133 log.Warningf("ignoring console socket since it cannot be restored") 134 } 135 136 if err := cont.Destroy(); err != nil { 137 util.Fatalf("destroying container: %v", err) 138 } 139 140 contArgs := container.Args{ 141 ID: id, 142 Spec: spec, 143 BundleDir: bundleDir, 144 } 145 cont, err = container.New(conf, contArgs) 146 if err != nil { 147 util.Fatalf("restoring container: %v", err) 148 } 149 defer cont.Destroy() 150 151 if err := cont.Restore(conf, fullImagePath); err != nil { 152 util.Fatalf("starting container: %v", err) 153 } 154 155 ws, err := cont.Wait() 156 if err != nil { 157 util.Fatalf("Error waiting for container: %v", err) 158 } 159 *waitStatus = ws 160 161 return subcommands.ExitSuccess 162 } 163 164 // CheckpointCompression represents checkpoint image writer behavior. The 165 // default behavior is to compress because the default behavior used to be to 166 // always compress. 167 type CheckpointCompression statefile.CompressionLevel 168 169 func newCheckpointCompressionValue(val statefile.CompressionLevel, p *CheckpointCompression) *CheckpointCompression { 170 *p = CheckpointCompression(val) 171 return (*CheckpointCompression)(p) 172 } 173 174 // Set implements flag.Value. 175 func (g *CheckpointCompression) Set(v string) error { 176 t, err := statefile.CompressionLevelFromString(v) 177 if err != nil { 178 return fmt.Errorf("invalid checkpoint compression type %q", v) 179 } 180 181 *g = CheckpointCompression(t) 182 183 return nil 184 } 185 186 // Get implements flag.Getter. 187 func (g *CheckpointCompression) Get() any { 188 return *g 189 } 190 191 // String implements flag.Value. 192 func (g CheckpointCompression) String() string { 193 return string(g) 194 } 195 196 // Level returns corresponding statefile.CompressionLevel value. 197 func (g CheckpointCompression) Level() statefile.CompressionLevel { 198 return statefile.CompressionLevel(g) 199 }