github.com/containerd/Containerd@v1.4.13/runtime/v2/runc/container.go (about) 1 // +build linux 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package runc 20 21 import ( 22 "context" 23 "encoding/json" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "sync" 28 29 "github.com/containerd/cgroups" 30 cgroupsv2 "github.com/containerd/cgroups/v2" 31 "github.com/containerd/console" 32 "github.com/containerd/containerd/errdefs" 33 "github.com/containerd/containerd/mount" 34 "github.com/containerd/containerd/namespaces" 35 "github.com/containerd/containerd/pkg/process" 36 "github.com/containerd/containerd/pkg/stdio" 37 "github.com/containerd/containerd/runtime/v2/runc/options" 38 "github.com/containerd/containerd/runtime/v2/task" 39 "github.com/containerd/typeurl" 40 "github.com/pkg/errors" 41 "github.com/sirupsen/logrus" 42 ) 43 44 // NewContainer returns a new runc container 45 func NewContainer(ctx context.Context, platform stdio.Platform, r *task.CreateTaskRequest) (_ *Container, retErr error) { 46 ns, err := namespaces.NamespaceRequired(ctx) 47 if err != nil { 48 return nil, errors.Wrap(err, "create namespace") 49 } 50 51 var opts options.Options 52 if r.Options != nil { 53 v, err := typeurl.UnmarshalAny(r.Options) 54 if err != nil { 55 return nil, err 56 } 57 opts = *v.(*options.Options) 58 } 59 60 var mounts []process.Mount 61 for _, m := range r.Rootfs { 62 mounts = append(mounts, process.Mount{ 63 Type: m.Type, 64 Source: m.Source, 65 Target: m.Target, 66 Options: m.Options, 67 }) 68 } 69 70 rootfs := "" 71 if len(mounts) > 0 { 72 rootfs = filepath.Join(r.Bundle, "rootfs") 73 if err := os.Mkdir(rootfs, 0711); err != nil && !os.IsExist(err) { 74 return nil, err 75 } 76 } 77 78 config := &process.CreateConfig{ 79 ID: r.ID, 80 Bundle: r.Bundle, 81 Runtime: opts.BinaryName, 82 Rootfs: mounts, 83 Terminal: r.Terminal, 84 Stdin: r.Stdin, 85 Stdout: r.Stdout, 86 Stderr: r.Stderr, 87 Checkpoint: r.Checkpoint, 88 ParentCheckpoint: r.ParentCheckpoint, 89 Options: r.Options, 90 } 91 92 if err := WriteOptions(r.Bundle, opts); err != nil { 93 return nil, err 94 } 95 // For historical reason, we write opts.BinaryName as well as the entire opts 96 if err := WriteRuntime(r.Bundle, opts.BinaryName); err != nil { 97 return nil, err 98 } 99 defer func() { 100 if retErr != nil { 101 if err := mount.UnmountAll(rootfs, 0); err != nil { 102 logrus.WithError(err).Warn("failed to cleanup rootfs mount") 103 } 104 } 105 }() 106 for _, rm := range mounts { 107 m := &mount.Mount{ 108 Type: rm.Type, 109 Source: rm.Source, 110 Options: rm.Options, 111 } 112 if err := m.Mount(rootfs); err != nil { 113 return nil, errors.Wrapf(err, "failed to mount rootfs component %v", m) 114 } 115 } 116 117 p, err := newInit( 118 ctx, 119 r.Bundle, 120 filepath.Join(r.Bundle, "work"), 121 ns, 122 platform, 123 config, 124 &opts, 125 rootfs, 126 ) 127 if err != nil { 128 return nil, errdefs.ToGRPC(err) 129 } 130 if err := p.Create(ctx, config); err != nil { 131 return nil, errdefs.ToGRPC(err) 132 } 133 container := &Container{ 134 ID: r.ID, 135 Bundle: r.Bundle, 136 process: p, 137 processes: make(map[string]process.Process), 138 reservedProcess: make(map[string]struct{}), 139 } 140 pid := p.Pid() 141 if pid > 0 { 142 var cg interface{} 143 if cgroups.Mode() == cgroups.Unified { 144 g, err := cgroupsv2.PidGroupPath(pid) 145 if err != nil { 146 logrus.WithError(err).Errorf("loading cgroup2 for %d", pid) 147 return container, nil 148 } 149 cg, err = cgroupsv2.LoadManager("/sys/fs/cgroup", g) 150 if err != nil { 151 logrus.WithError(err).Errorf("loading cgroup2 for %d", pid) 152 } 153 } else { 154 cg, err = cgroups.Load(cgroups.V1, cgroups.PidPath(pid)) 155 if err != nil { 156 logrus.WithError(err).Errorf("loading cgroup for %d", pid) 157 } 158 } 159 container.cgroup = cg 160 } 161 return container, nil 162 } 163 164 const optionsFilename = "options.json" 165 166 // ReadOptions reads the option information from the path. 167 // When the file does not exist, ReadOptions returns nil without an error. 168 func ReadOptions(path string) (*options.Options, error) { 169 filePath := filepath.Join(path, optionsFilename) 170 if _, err := os.Stat(filePath); err != nil { 171 if os.IsNotExist(err) { 172 return nil, nil 173 } 174 return nil, err 175 } 176 177 data, err := ioutil.ReadFile(filePath) 178 if err != nil { 179 return nil, err 180 } 181 var opts options.Options 182 if err := json.Unmarshal(data, &opts); err != nil { 183 return nil, err 184 } 185 return &opts, nil 186 } 187 188 // WriteOptions writes the options information into the path 189 func WriteOptions(path string, opts options.Options) error { 190 data, err := json.Marshal(opts) 191 if err != nil { 192 return err 193 } 194 return ioutil.WriteFile(filepath.Join(path, optionsFilename), data, 0600) 195 } 196 197 // ReadRuntime reads the runtime information from the path 198 func ReadRuntime(path string) (string, error) { 199 data, err := ioutil.ReadFile(filepath.Join(path, "runtime")) 200 if err != nil { 201 return "", err 202 } 203 return string(data), nil 204 } 205 206 // WriteRuntime writes the runtime information into the path 207 func WriteRuntime(path, runtime string) error { 208 return ioutil.WriteFile(filepath.Join(path, "runtime"), []byte(runtime), 0600) 209 } 210 211 func newInit(ctx context.Context, path, workDir, namespace string, platform stdio.Platform, 212 r *process.CreateConfig, options *options.Options, rootfs string) (*process.Init, error) { 213 runtime := process.NewRunc(options.Root, path, namespace, options.BinaryName, options.CriuPath, options.SystemdCgroup) 214 p := process.New(r.ID, runtime, stdio.Stdio{ 215 Stdin: r.Stdin, 216 Stdout: r.Stdout, 217 Stderr: r.Stderr, 218 Terminal: r.Terminal, 219 }) 220 p.Bundle = r.Bundle 221 p.Platform = platform 222 p.Rootfs = rootfs 223 p.WorkDir = workDir 224 p.IoUID = int(options.IoUid) 225 p.IoGID = int(options.IoGid) 226 p.NoPivotRoot = options.NoPivotRoot 227 p.NoNewKeyring = options.NoNewKeyring 228 p.CriuWorkPath = options.CriuWorkPath 229 if p.CriuWorkPath == "" { 230 // if criu work path not set, use container WorkDir 231 p.CriuWorkPath = p.WorkDir 232 } 233 return p, nil 234 } 235 236 // Container for operating on a runc container and its processes 237 type Container struct { 238 mu sync.Mutex 239 240 // ID of the container 241 ID string 242 // Bundle path 243 Bundle string 244 245 // cgroup is either cgroups.Cgroup or *cgroupsv2.Manager 246 cgroup interface{} 247 process process.Process 248 processes map[string]process.Process 249 reservedProcess map[string]struct{} 250 } 251 252 // All processes in the container 253 func (c *Container) All() (o []process.Process) { 254 c.mu.Lock() 255 defer c.mu.Unlock() 256 257 for _, p := range c.processes { 258 o = append(o, p) 259 } 260 if c.process != nil { 261 o = append(o, c.process) 262 } 263 return o 264 } 265 266 // ExecdProcesses added to the container 267 func (c *Container) ExecdProcesses() (o []process.Process) { 268 c.mu.Lock() 269 defer c.mu.Unlock() 270 for _, p := range c.processes { 271 o = append(o, p) 272 } 273 return o 274 } 275 276 // Pid of the main process of a container 277 func (c *Container) Pid() int { 278 c.mu.Lock() 279 defer c.mu.Unlock() 280 return c.process.Pid() 281 } 282 283 // Cgroup of the container 284 func (c *Container) Cgroup() interface{} { 285 c.mu.Lock() 286 defer c.mu.Unlock() 287 return c.cgroup 288 } 289 290 // CgroupSet sets the cgroup to the container 291 func (c *Container) CgroupSet(cg interface{}) { 292 c.mu.Lock() 293 c.cgroup = cg 294 c.mu.Unlock() 295 } 296 297 // Process returns the process by id 298 func (c *Container) Process(id string) (process.Process, error) { 299 c.mu.Lock() 300 defer c.mu.Unlock() 301 if id == "" { 302 if c.process == nil { 303 return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "container must be created") 304 } 305 return c.process, nil 306 } 307 p, ok := c.processes[id] 308 if !ok { 309 return nil, errors.Wrapf(errdefs.ErrNotFound, "process does not exist %s", id) 310 } 311 return p, nil 312 } 313 314 // ReserveProcess checks for the existence of an id and atomically 315 // reserves the process id if it does not already exist 316 // 317 // Returns true if the process id was successfully reserved and a 318 // cancel func to release the reservation 319 func (c *Container) ReserveProcess(id string) (bool, func()) { 320 c.mu.Lock() 321 defer c.mu.Unlock() 322 323 if _, ok := c.processes[id]; ok { 324 return false, nil 325 } 326 if _, ok := c.reservedProcess[id]; ok { 327 return false, nil 328 } 329 c.reservedProcess[id] = struct{}{} 330 return true, func() { 331 c.mu.Lock() 332 defer c.mu.Unlock() 333 delete(c.reservedProcess, id) 334 } 335 } 336 337 // ProcessAdd adds a new process to the container 338 func (c *Container) ProcessAdd(process process.Process) { 339 c.mu.Lock() 340 defer c.mu.Unlock() 341 342 delete(c.reservedProcess, process.ID()) 343 c.processes[process.ID()] = process 344 } 345 346 // ProcessRemove removes the process by id from the container 347 func (c *Container) ProcessRemove(id string) { 348 c.mu.Lock() 349 defer c.mu.Unlock() 350 delete(c.processes, id) 351 } 352 353 // Start a container process 354 func (c *Container) Start(ctx context.Context, r *task.StartRequest) (process.Process, error) { 355 p, err := c.Process(r.ExecID) 356 if err != nil { 357 return nil, err 358 } 359 if err := p.Start(ctx); err != nil { 360 return nil, err 361 } 362 if c.Cgroup() == nil && p.Pid() > 0 { 363 var cg interface{} 364 if cgroups.Mode() == cgroups.Unified { 365 g, err := cgroupsv2.PidGroupPath(p.Pid()) 366 if err != nil { 367 logrus.WithError(err).Errorf("loading cgroup2 for %d", p.Pid()) 368 } 369 cg, err = cgroupsv2.LoadManager("/sys/fs/cgroup", g) 370 if err != nil { 371 logrus.WithError(err).Errorf("loading cgroup2 for %d", p.Pid()) 372 } 373 } else { 374 cg, err = cgroups.Load(cgroups.V1, cgroups.PidPath(p.Pid())) 375 if err != nil { 376 logrus.WithError(err).Errorf("loading cgroup for %d", p.Pid()) 377 } 378 } 379 c.cgroup = cg 380 } 381 return p, nil 382 } 383 384 // Delete the container or a process by id 385 func (c *Container) Delete(ctx context.Context, r *task.DeleteRequest) (process.Process, error) { 386 p, err := c.Process(r.ExecID) 387 if err != nil { 388 return nil, err 389 } 390 if err := p.Delete(ctx); err != nil { 391 return nil, err 392 } 393 if r.ExecID != "" { 394 c.ProcessRemove(r.ExecID) 395 } 396 return p, nil 397 } 398 399 // Exec an additional process 400 func (c *Container) Exec(ctx context.Context, r *task.ExecProcessRequest) (process.Process, error) { 401 process, err := c.process.(*process.Init).Exec(ctx, c.Bundle, &process.ExecConfig{ 402 ID: r.ExecID, 403 Terminal: r.Terminal, 404 Stdin: r.Stdin, 405 Stdout: r.Stdout, 406 Stderr: r.Stderr, 407 Spec: r.Spec, 408 }) 409 if err != nil { 410 return nil, err 411 } 412 c.ProcessAdd(process) 413 return process, nil 414 } 415 416 // Pause the container 417 func (c *Container) Pause(ctx context.Context) error { 418 return c.process.(*process.Init).Pause(ctx) 419 } 420 421 // Resume the container 422 func (c *Container) Resume(ctx context.Context) error { 423 return c.process.(*process.Init).Resume(ctx) 424 } 425 426 // ResizePty of a process 427 func (c *Container) ResizePty(ctx context.Context, r *task.ResizePtyRequest) error { 428 p, err := c.Process(r.ExecID) 429 if err != nil { 430 return err 431 } 432 ws := console.WinSize{ 433 Width: uint16(r.Width), 434 Height: uint16(r.Height), 435 } 436 return p.Resize(ws) 437 } 438 439 // Kill a process 440 func (c *Container) Kill(ctx context.Context, r *task.KillRequest) error { 441 p, err := c.Process(r.ExecID) 442 if err != nil { 443 return err 444 } 445 return p.Kill(ctx, r.Signal, r.All) 446 } 447 448 // CloseIO of a process 449 func (c *Container) CloseIO(ctx context.Context, r *task.CloseIORequest) error { 450 p, err := c.Process(r.ExecID) 451 if err != nil { 452 return err 453 } 454 if stdin := p.Stdin(); stdin != nil { 455 if err := stdin.Close(); err != nil { 456 return errors.Wrap(err, "close stdin") 457 } 458 } 459 return nil 460 } 461 462 // Checkpoint the container 463 func (c *Container) Checkpoint(ctx context.Context, r *task.CheckpointTaskRequest) error { 464 p, err := c.Process("") 465 if err != nil { 466 return err 467 } 468 var opts options.CheckpointOptions 469 if r.Options != nil { 470 v, err := typeurl.UnmarshalAny(r.Options) 471 if err != nil { 472 return err 473 } 474 opts = *v.(*options.CheckpointOptions) 475 } 476 return p.(*process.Init).Checkpoint(ctx, &process.CheckpointConfig{ 477 Path: r.Path, 478 Exit: opts.Exit, 479 AllowOpenTCP: opts.OpenTcp, 480 AllowExternalUnixSockets: opts.ExternalUnixSockets, 481 AllowTerminal: opts.Terminal, 482 FileLocks: opts.FileLocks, 483 EmptyNamespaces: opts.EmptyNamespaces, 484 WorkDir: opts.WorkPath, 485 }) 486 } 487 488 // Update the resource information of a running container 489 func (c *Container) Update(ctx context.Context, r *task.UpdateTaskRequest) error { 490 p, err := c.Process("") 491 if err != nil { 492 return err 493 } 494 return p.(*process.Init).Update(ctx, r.Resources) 495 } 496 497 // HasPid returns true if the container owns a specific pid 498 func (c *Container) HasPid(pid int) bool { 499 if c.Pid() == pid { 500 return true 501 } 502 for _, p := range c.All() { 503 if p.Pid() == pid { 504 return true 505 } 506 } 507 return false 508 }