github.com/dims/containerd@v0.2.5/runtime/container.go (about) 1 package runtime 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "syscall" 12 "time" 13 14 "github.com/Sirupsen/logrus" 15 "github.com/docker/containerd/specs" 16 ocs "github.com/opencontainers/runtime-spec/specs-go" 17 "golang.org/x/sys/unix" 18 ) 19 20 // Container defines the operations allowed on a container 21 type Container interface { 22 // ID returns the container ID 23 ID() string 24 // Path returns the path to the bundle 25 Path() string 26 // Start starts the init process of the container 27 Start(checkpointPath string, s Stdio) (Process, error) 28 // Exec starts another process in an existing container 29 Exec(string, specs.ProcessSpec, Stdio) (Process, error) 30 // Delete removes the container's state and any resources 31 Delete() error 32 // Processes returns all the containers processes that have been added 33 Processes() ([]Process, error) 34 // State returns the containers runtime state 35 State() State 36 // Resume resumes a paused container 37 Resume() error 38 // Pause pauses a running container 39 Pause() error 40 // RemoveProcess removes the specified process from the container 41 RemoveProcess(string) error 42 // Checkpoints returns all the checkpoints for a container 43 Checkpoints(checkpointDir string) ([]Checkpoint, error) 44 // Checkpoint creates a new checkpoint 45 Checkpoint(checkpoint Checkpoint, checkpointDir string) error 46 // DeleteCheckpoint deletes the checkpoint for the provided name 47 DeleteCheckpoint(name string, checkpointDir string) error 48 // Labels are user provided labels for the container 49 Labels() []string 50 // Pids returns all pids inside the container 51 Pids() ([]int, error) 52 // Stats returns realtime container stats and resource information 53 Stats() (*Stat, error) 54 // Name or path of the OCI compliant runtime used to execute the container 55 Runtime() string 56 // OOM signals the channel if the container received an OOM notification 57 OOM() (OOM, error) 58 // UpdateResource updates the containers resources to new values 59 UpdateResources(*Resource) error 60 61 // Status return the current status of the container. 62 Status() (State, error) 63 } 64 65 // OOM wraps a container OOM. 66 type OOM interface { 67 io.Closer 68 FD() int 69 ContainerID() string 70 Flush() 71 Removed() bool 72 } 73 74 // Stdio holds the path to the 3 pipes used for the standard ios. 75 type Stdio struct { 76 Stdin string 77 Stdout string 78 Stderr string 79 } 80 81 // NewStdio wraps the given standard io path into an Stdio struct. 82 // If a given parameter is the empty string, it is replaced by "/dev/null" 83 func NewStdio(stdin, stdout, stderr string) Stdio { 84 for _, s := range []*string{ 85 &stdin, &stdout, &stderr, 86 } { 87 if *s == "" { 88 *s = "/dev/null" 89 } 90 } 91 return Stdio{ 92 Stdin: stdin, 93 Stdout: stdout, 94 Stderr: stderr, 95 } 96 } 97 98 // ContainerOpts keeps the options passed at container creation 99 type ContainerOpts struct { 100 Root string 101 ID string 102 Bundle string 103 Runtime string 104 RuntimeArgs []string 105 Shim string 106 Labels []string 107 NoPivotRoot bool 108 Timeout time.Duration 109 } 110 111 // New returns a new container 112 func New(opts ContainerOpts) (Container, error) { 113 c := &container{ 114 root: opts.Root, 115 id: opts.ID, 116 bundle: opts.Bundle, 117 labels: opts.Labels, 118 processes: make(map[string]*process), 119 runtime: opts.Runtime, 120 runtimeArgs: opts.RuntimeArgs, 121 shim: opts.Shim, 122 noPivotRoot: opts.NoPivotRoot, 123 timeout: opts.Timeout, 124 } 125 if err := os.Mkdir(filepath.Join(c.root, c.id), 0755); err != nil { 126 return nil, err 127 } 128 f, err := os.Create(filepath.Join(c.root, c.id, StateFile)) 129 if err != nil { 130 return nil, err 131 } 132 defer f.Close() 133 if err := json.NewEncoder(f).Encode(state{ 134 Bundle: c.bundle, 135 Labels: c.labels, 136 Runtime: c.runtime, 137 RuntimeArgs: c.runtimeArgs, 138 Shim: c.shim, 139 NoPivotRoot: opts.NoPivotRoot, 140 }); err != nil { 141 return nil, err 142 } 143 return c, nil 144 } 145 146 // Load return a new container from the matchin state file on disk. 147 func Load(root, id, shimName string, timeout time.Duration) (Container, error) { 148 var s state 149 f, err := os.Open(filepath.Join(root, id, StateFile)) 150 if err != nil { 151 return nil, err 152 } 153 defer f.Close() 154 if err := json.NewDecoder(f).Decode(&s); err != nil { 155 return nil, err 156 } 157 c := &container{ 158 root: root, 159 id: id, 160 bundle: s.Bundle, 161 labels: s.Labels, 162 runtime: s.Runtime, 163 runtimeArgs: s.RuntimeArgs, 164 shim: s.Shim, 165 noPivotRoot: s.NoPivotRoot, 166 processes: make(map[string]*process), 167 timeout: timeout, 168 } 169 170 if c.shim == "" { 171 c.shim = shimName 172 } 173 174 dirs, err := ioutil.ReadDir(filepath.Join(root, id)) 175 if err != nil { 176 return nil, err 177 } 178 for _, d := range dirs { 179 if !d.IsDir() { 180 continue 181 } 182 pid := d.Name() 183 s, err := readProcessState(filepath.Join(root, id, pid)) 184 if err != nil { 185 return nil, err 186 } 187 p, err := loadProcess(filepath.Join(root, id, pid), pid, c, s) 188 if err != nil { 189 logrus.WithField("id", id).WithField("pid", pid).Debug("containerd: error loading process %s", err) 190 continue 191 } 192 c.processes[pid] = p 193 } 194 return c, nil 195 } 196 197 func readProcessState(dir string) (*ProcessState, error) { 198 f, err := os.Open(filepath.Join(dir, "process.json")) 199 if err != nil { 200 return nil, err 201 } 202 defer f.Close() 203 var s ProcessState 204 if err := json.NewDecoder(f).Decode(&s); err != nil { 205 return nil, err 206 } 207 return &s, nil 208 } 209 210 type container struct { 211 // path to store runtime state information 212 root string 213 id string 214 bundle string 215 runtime string 216 runtimeArgs []string 217 shim string 218 processes map[string]*process 219 labels []string 220 oomFds []int 221 noPivotRoot bool 222 timeout time.Duration 223 } 224 225 func (c *container) ID() string { 226 return c.id 227 } 228 229 func (c *container) Path() string { 230 return c.bundle 231 } 232 233 func (c *container) Labels() []string { 234 return c.labels 235 } 236 237 func (c *container) readSpec() (*specs.Spec, error) { 238 var spec specs.Spec 239 f, err := os.Open(filepath.Join(c.bundle, "config.json")) 240 if err != nil { 241 return nil, err 242 } 243 defer f.Close() 244 if err := json.NewDecoder(f).Decode(&spec); err != nil { 245 return nil, err 246 } 247 return &spec, nil 248 } 249 250 func (c *container) Delete() error { 251 err := os.RemoveAll(filepath.Join(c.root, c.id)) 252 253 args := c.runtimeArgs 254 args = append(args, "delete", c.id) 255 if b, derr := exec.Command(c.runtime, args...).CombinedOutput(); err != nil { 256 err = fmt.Errorf("%s: %q", derr, string(b)) 257 } else if len(b) > 0 { 258 logrus.Debugf("%v %v: %q", c.runtime, args, string(b)) 259 } 260 return err 261 } 262 263 func (c *container) Processes() ([]Process, error) { 264 out := []Process{} 265 for _, p := range c.processes { 266 out = append(out, p) 267 } 268 return out, nil 269 } 270 271 func (c *container) RemoveProcess(pid string) error { 272 delete(c.processes, pid) 273 return os.RemoveAll(filepath.Join(c.root, c.id, pid)) 274 } 275 276 func (c *container) State() State { 277 proc := c.processes["init"] 278 if proc == nil { 279 return Stopped 280 } 281 return proc.State() 282 } 283 284 func (c *container) Runtime() string { 285 return c.runtime 286 } 287 288 func (c *container) Pause() error { 289 args := c.runtimeArgs 290 args = append(args, "pause", c.id) 291 b, err := exec.Command(c.runtime, args...).CombinedOutput() 292 if err != nil { 293 return fmt.Errorf("%s: %q", err.Error(), string(b)) 294 } 295 return nil 296 } 297 298 func (c *container) Resume() error { 299 args := c.runtimeArgs 300 args = append(args, "resume", c.id) 301 b, err := exec.Command(c.runtime, args...).CombinedOutput() 302 if err != nil { 303 return fmt.Errorf("%s: %q", err.Error(), string(b)) 304 } 305 return nil 306 } 307 308 func (c *container) Checkpoints(checkpointDir string) ([]Checkpoint, error) { 309 if checkpointDir == "" { 310 checkpointDir = filepath.Join(c.bundle, "checkpoints") 311 } 312 313 dirs, err := ioutil.ReadDir(checkpointDir) 314 if err != nil { 315 return nil, err 316 } 317 var out []Checkpoint 318 for _, d := range dirs { 319 if !d.IsDir() { 320 continue 321 } 322 path := filepath.Join(checkpointDir, d.Name(), "config.json") 323 data, err := ioutil.ReadFile(path) 324 if err != nil { 325 return nil, err 326 } 327 var cpt Checkpoint 328 if err := json.Unmarshal(data, &cpt); err != nil { 329 return nil, err 330 } 331 out = append(out, cpt) 332 } 333 return out, nil 334 } 335 336 func (c *container) Checkpoint(cpt Checkpoint, checkpointDir string) error { 337 if checkpointDir == "" { 338 checkpointDir = filepath.Join(c.bundle, "checkpoints") 339 } 340 341 if err := os.MkdirAll(checkpointDir, 0755); err != nil { 342 return err 343 } 344 345 path := filepath.Join(checkpointDir, cpt.Name) 346 if err := os.Mkdir(path, 0755); err != nil { 347 return err 348 } 349 f, err := os.Create(filepath.Join(path, "config.json")) 350 if err != nil { 351 return err 352 } 353 cpt.Created = time.Now() 354 err = json.NewEncoder(f).Encode(cpt) 355 f.Close() 356 if err != nil { 357 return err 358 } 359 args := []string{ 360 "checkpoint", 361 "--image-path", path, 362 "--work-path", filepath.Join(path, "criu.work"), 363 } 364 add := func(flags ...string) { 365 args = append(args, flags...) 366 } 367 add(c.runtimeArgs...) 368 if !cpt.Exit { 369 add("--leave-running") 370 } 371 if cpt.Shell { 372 add("--shell-job") 373 } 374 if cpt.TCP { 375 add("--tcp-established") 376 } 377 if cpt.UnixSockets { 378 add("--ext-unix-sk") 379 } 380 for _, ns := range cpt.EmptyNS { 381 add("--empty-ns", ns) 382 } 383 add(c.id) 384 out, err := exec.Command(c.runtime, args...).CombinedOutput() 385 if err != nil { 386 return fmt.Errorf("%s: %q", err.Error(), string(out)) 387 } 388 return err 389 } 390 391 func (c *container) DeleteCheckpoint(name string, checkpointDir string) error { 392 if checkpointDir == "" { 393 checkpointDir = filepath.Join(c.bundle, "checkpoints") 394 } 395 return os.RemoveAll(filepath.Join(checkpointDir, name)) 396 } 397 398 func (c *container) Start(checkpointPath string, s Stdio) (Process, error) { 399 processRoot := filepath.Join(c.root, c.id, InitProcessID) 400 if err := os.Mkdir(processRoot, 0755); err != nil { 401 return nil, err 402 } 403 cmd := exec.Command(c.shim, 404 c.id, c.bundle, c.runtime, 405 ) 406 cmd.Dir = processRoot 407 cmd.SysProcAttr = &syscall.SysProcAttr{ 408 Setpgid: true, 409 } 410 spec, err := c.readSpec() 411 if err != nil { 412 return nil, err 413 } 414 config := &processConfig{ 415 checkpoint: checkpointPath, 416 root: processRoot, 417 id: InitProcessID, 418 c: c, 419 stdio: s, 420 spec: spec, 421 processSpec: specs.ProcessSpec(spec.Process), 422 } 423 p, err := newProcess(config) 424 if err != nil { 425 return nil, err 426 } 427 if err := c.createCmd(InitProcessID, cmd, p); err != nil { 428 return nil, err 429 } 430 return p, nil 431 } 432 433 func (c *container) Exec(pid string, pspec specs.ProcessSpec, s Stdio) (pp Process, err error) { 434 processRoot := filepath.Join(c.root, c.id, pid) 435 if err := os.Mkdir(processRoot, 0755); err != nil { 436 return nil, err 437 } 438 defer func() { 439 if err != nil { 440 c.RemoveProcess(pid) 441 } 442 }() 443 cmd := exec.Command(c.shim, 444 c.id, c.bundle, c.runtime, 445 ) 446 cmd.Dir = processRoot 447 cmd.SysProcAttr = &syscall.SysProcAttr{ 448 Setpgid: true, 449 } 450 spec, err := c.readSpec() 451 if err != nil { 452 return nil, err 453 } 454 config := &processConfig{ 455 exec: true, 456 id: pid, 457 root: processRoot, 458 c: c, 459 processSpec: pspec, 460 spec: spec, 461 stdio: s, 462 } 463 p, err := newProcess(config) 464 if err != nil { 465 return nil, err 466 } 467 if err := c.createCmd(pid, cmd, p); err != nil { 468 return nil, err 469 } 470 return p, nil 471 } 472 473 func (c *container) createCmd(pid string, cmd *exec.Cmd, p *process) error { 474 p.cmd = cmd 475 if err := cmd.Start(); err != nil { 476 close(p.cmdDoneCh) 477 if exErr, ok := err.(*exec.Error); ok { 478 if exErr.Err == exec.ErrNotFound || exErr.Err == os.ErrNotExist { 479 return fmt.Errorf("%s not installed on system", c.shim) 480 } 481 } 482 return err 483 } 484 // We need the pid file to have been written to run 485 defer func() { 486 go func() { 487 err := p.cmd.Wait() 488 if err == nil { 489 p.cmdSuccess = true 490 } 491 492 if same, err := p.isSameProcess(); same && p.pid > 0 { 493 // The process changed its PR_SET_PDEATHSIG, so force 494 // kill it 495 logrus.Infof("containerd: %s:%s (pid %v) has become an orphan, killing it", p.container.id, p.id, p.pid) 496 err = unix.Kill(p.pid, syscall.SIGKILL) 497 if err != nil && err != syscall.ESRCH { 498 logrus.Errorf("containerd: unable to SIGKILL %s:%s (pid %v): %v", p.container.id, p.id, p.pid, err) 499 } else { 500 for { 501 err = unix.Kill(p.pid, 0) 502 if err != nil { 503 break 504 } 505 time.Sleep(5 * time.Millisecond) 506 } 507 } 508 } 509 close(p.cmdDoneCh) 510 }() 511 }() 512 if err := c.waitForCreate(p, cmd); err != nil { 513 return err 514 } 515 c.processes[pid] = p 516 return nil 517 } 518 519 func hostIDFromMap(id uint32, mp []ocs.IDMapping) int { 520 for _, m := range mp { 521 if (id >= m.ContainerID) && (id <= (m.ContainerID + m.Size - 1)) { 522 return int(m.HostID + (id - m.ContainerID)) 523 } 524 } 525 return 0 526 } 527 528 func (c *container) Pids() ([]int, error) { 529 args := c.runtimeArgs 530 args = append(args, "ps", "--format=json", c.id) 531 out, err := exec.Command(c.runtime, args...).CombinedOutput() 532 if err != nil { 533 return nil, fmt.Errorf("%s: %q", err.Error(), out) 534 } 535 var pids []int 536 if err := json.Unmarshal(out, &pids); err != nil { 537 return nil, err 538 } 539 return pids, nil 540 } 541 542 func (c *container) Stats() (*Stat, error) { 543 now := time.Now() 544 args := c.runtimeArgs 545 args = append(args, "events", "--stats", c.id) 546 out, err := exec.Command(c.runtime, args...).CombinedOutput() 547 if err != nil { 548 return nil, fmt.Errorf("%s: %q", err.Error(), out) 549 } 550 s := struct { 551 Data *Stat `json:"data"` 552 }{} 553 if err := json.Unmarshal(out, &s); err != nil { 554 return nil, err 555 } 556 s.Data.Timestamp = now 557 return s.Data, nil 558 } 559 560 // Status implements the runtime Container interface. 561 func (c *container) Status() (State, error) { 562 args := c.runtimeArgs 563 args = append(args, "state", c.id) 564 565 out, err := exec.Command(c.runtime, args...).CombinedOutput() 566 if err != nil { 567 return "", fmt.Errorf("%s: %q", err.Error(), out) 568 } 569 570 // We only require the runtime json output to have a top level Status field. 571 var s struct { 572 Status State `json:"status"` 573 } 574 if err := json.Unmarshal(out, &s); err != nil { 575 return "", err 576 } 577 return s.Status, nil 578 } 579 580 func (c *container) writeEventFD(root string, cfd, efd int) error { 581 f, err := os.OpenFile(filepath.Join(root, "cgroup.event_control"), os.O_WRONLY, 0) 582 if err != nil { 583 return err 584 } 585 defer f.Close() 586 _, err = f.WriteString(fmt.Sprintf("%d %d", efd, cfd)) 587 return err 588 } 589 590 type waitArgs struct { 591 pid int 592 err error 593 } 594 595 func (c *container) waitForCreate(p *process, cmd *exec.Cmd) error { 596 wc := make(chan error, 1) 597 go func() { 598 for { 599 if _, err := p.getPidFromFile(); err != nil { 600 if os.IsNotExist(err) || err == errInvalidPidInt { 601 alive, err := isAlive(cmd) 602 if err != nil { 603 wc <- err 604 return 605 } 606 if !alive { 607 // runc could have failed to run the container so lets get the error 608 // out of the logs or the shim could have encountered an error 609 messages, err := readLogMessages(filepath.Join(p.root, "shim-log.json")) 610 if err != nil { 611 wc <- err 612 return 613 } 614 for _, m := range messages { 615 if m.Level == "error" { 616 wc <- fmt.Errorf("shim error: %v", m.Msg) 617 return 618 } 619 } 620 // no errors reported back from shim, check for runc/runtime errors 621 messages, err = readLogMessages(filepath.Join(p.root, "log.json")) 622 if err != nil { 623 if os.IsNotExist(err) { 624 err = ErrContainerNotStarted 625 } 626 wc <- err 627 return 628 } 629 for _, m := range messages { 630 if m.Level == "error" { 631 wc <- fmt.Errorf("oci runtime error: %v", m.Msg) 632 return 633 } 634 } 635 wc <- ErrContainerNotStarted 636 return 637 } 638 time.Sleep(15 * time.Millisecond) 639 continue 640 } 641 wc <- err 642 return 643 } 644 // the pid file was read successfully 645 wc <- nil 646 return 647 } 648 }() 649 select { 650 case err := <-wc: 651 if err != nil { 652 return err 653 } 654 err = p.saveStartTime() 655 if err != nil { 656 logrus.Warnf("containerd: unable to save %s:%s starttime: %v", p.container.id, p.id, err) 657 } 658 return nil 659 case <-time.After(c.timeout): 660 cmd.Process.Kill() 661 cmd.Wait() 662 return ErrContainerStartTimeout 663 } 664 } 665 666 // isAlive checks if the shim that launched the container is still alive 667 func isAlive(cmd *exec.Cmd) (bool, error) { 668 if _, err := syscall.Wait4(cmd.Process.Pid, nil, syscall.WNOHANG, nil); err == nil { 669 return true, nil 670 } 671 if err := syscall.Kill(cmd.Process.Pid, 0); err != nil { 672 if err == syscall.ESRCH { 673 return false, nil 674 } 675 return false, err 676 } 677 return true, nil 678 } 679 680 type oom struct { 681 id string 682 root string 683 control *os.File 684 eventfd int 685 } 686 687 func (o *oom) ContainerID() string { 688 return o.id 689 } 690 691 func (o *oom) FD() int { 692 return o.eventfd 693 } 694 695 func (o *oom) Flush() { 696 buf := make([]byte, 8) 697 syscall.Read(o.eventfd, buf) 698 } 699 700 func (o *oom) Removed() bool { 701 _, err := os.Lstat(filepath.Join(o.root, "cgroup.event_control")) 702 return os.IsNotExist(err) 703 } 704 705 func (o *oom) Close() error { 706 err := syscall.Close(o.eventfd) 707 if cerr := o.control.Close(); err == nil { 708 err = cerr 709 } 710 return err 711 } 712 713 type message struct { 714 Level string `json:"level"` 715 Msg string `json:"msg"` 716 } 717 718 func readLogMessages(path string) ([]message, error) { 719 var out []message 720 f, err := os.Open(path) 721 if err != nil { 722 return nil, err 723 } 724 defer f.Close() 725 dec := json.NewDecoder(f) 726 for { 727 var m message 728 if err := dec.Decode(&m); err != nil { 729 if err == io.EOF { 730 break 731 } 732 return nil, err 733 } 734 out = append(out, m) 735 } 736 return out, nil 737 }