github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/runtime/v2/runc/v1/service.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 v1 20 21 import ( 22 "context" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "sync" 28 "syscall" 29 "time" 30 31 "github.com/containerd/cgroups" 32 eventstypes "github.com/containerd/containerd/api/events" 33 "github.com/containerd/containerd/api/types/task" 34 "github.com/containerd/containerd/errdefs" 35 "github.com/containerd/containerd/mount" 36 "github.com/containerd/containerd/namespaces" 37 "github.com/containerd/containerd/pkg/oom" 38 oomv1 "github.com/containerd/containerd/pkg/oom/v1" 39 "github.com/containerd/containerd/pkg/process" 40 "github.com/containerd/containerd/pkg/stdio" 41 "github.com/containerd/containerd/runtime/v2/runc" 42 "github.com/containerd/containerd/runtime/v2/runc/options" 43 "github.com/containerd/containerd/runtime/v2/shim" 44 taskAPI "github.com/containerd/containerd/runtime/v2/task" 45 "github.com/containerd/containerd/sys/reaper" 46 runcC "github.com/containerd/go-runc" 47 "github.com/containerd/typeurl" 48 "github.com/gogo/protobuf/proto" 49 ptypes "github.com/gogo/protobuf/types" 50 "github.com/pkg/errors" 51 "github.com/sirupsen/logrus" 52 "golang.org/x/sys/unix" 53 ) 54 55 var ( 56 _ = (taskAPI.TaskService)(&service{}) 57 empty = &ptypes.Empty{} 58 ) 59 60 // New returns a new shim service that can be used via GRPC 61 func New(ctx context.Context, id string, publisher shim.Publisher, shutdown func()) (shim.Shim, error) { 62 ep, err := oomv1.New(publisher) 63 if err != nil { 64 return nil, err 65 } 66 go ep.Run(ctx) 67 s := &service{ 68 id: id, 69 context: ctx, 70 events: make(chan interface{}, 128), 71 ec: reaper.Default.Subscribe(), 72 ep: ep, 73 cancel: shutdown, 74 } 75 go s.processExits() 76 runcC.Monitor = reaper.Default 77 if err := s.initPlatform(); err != nil { 78 shutdown() 79 return nil, errors.Wrap(err, "failed to initialized platform behavior") 80 } 81 go s.forward(ctx, publisher) 82 return s, nil 83 } 84 85 // service is the shim implementation of a remote shim over GRPC 86 type service struct { 87 mu sync.Mutex 88 eventSendMu sync.Mutex 89 90 context context.Context 91 events chan interface{} 92 platform stdio.Platform 93 ec chan runcC.Exit 94 ep oom.Watcher 95 96 id string 97 container *runc.Container 98 99 cancel func() 100 } 101 102 func newCommand(ctx context.Context, id, containerdBinary, containerdAddress, containerdTTRPCAddress string) (*exec.Cmd, error) { 103 ns, err := namespaces.NamespaceRequired(ctx) 104 if err != nil { 105 return nil, err 106 } 107 self, err := os.Executable() 108 if err != nil { 109 return nil, err 110 } 111 cwd, err := os.Getwd() 112 if err != nil { 113 return nil, err 114 } 115 args := []string{ 116 "-namespace", ns, 117 "-id", id, 118 "-address", containerdAddress, 119 } 120 cmd := exec.Command(self, args...) 121 cmd.Dir = cwd 122 cmd.Env = append(os.Environ(), "GOMAXPROCS=2") 123 cmd.SysProcAttr = &syscall.SysProcAttr{ 124 Setpgid: true, 125 } 126 return cmd, nil 127 } 128 129 func (s *service) StartShim(ctx context.Context, id, containerdBinary, containerdAddress, containerdTTRPCAddress string) (string, error) { 130 cmd, err := newCommand(ctx, id, containerdBinary, containerdAddress, containerdTTRPCAddress) 131 if err != nil { 132 return "", err 133 } 134 address, err := shim.SocketAddress(ctx, id) 135 if err != nil { 136 return "", err 137 } 138 socket, err := shim.NewSocket(address) 139 if err != nil { 140 return "", err 141 } 142 defer socket.Close() 143 f, err := socket.File() 144 if err != nil { 145 return "", err 146 } 147 defer f.Close() 148 149 cmd.ExtraFiles = append(cmd.ExtraFiles, f) 150 151 if err := cmd.Start(); err != nil { 152 return "", err 153 } 154 defer func() { 155 if err != nil { 156 cmd.Process.Kill() 157 } 158 }() 159 // make sure to wait after start 160 go cmd.Wait() 161 if err := shim.WritePidFile("shim.pid", cmd.Process.Pid); err != nil { 162 return "", err 163 } 164 if err := shim.WriteAddress("address", address); err != nil { 165 return "", err 166 } 167 if data, err := ioutil.ReadAll(os.Stdin); err == nil { 168 if len(data) > 0 { 169 var any ptypes.Any 170 if err := proto.Unmarshal(data, &any); err != nil { 171 return "", err 172 } 173 v, err := typeurl.UnmarshalAny(&any) 174 if err != nil { 175 return "", err 176 } 177 if opts, ok := v.(*options.Options); ok { 178 if opts.ShimCgroup != "" { 179 cg, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(opts.ShimCgroup)) 180 if err != nil { 181 return "", errors.Wrapf(err, "failed to load cgroup %s", opts.ShimCgroup) 182 } 183 if err := cg.Add(cgroups.Process{ 184 Pid: cmd.Process.Pid, 185 }); err != nil { 186 return "", errors.Wrapf(err, "failed to join cgroup %s", opts.ShimCgroup) 187 } 188 } 189 } 190 } 191 } 192 if err := shim.AdjustOOMScore(cmd.Process.Pid); err != nil { 193 return "", errors.Wrap(err, "failed to adjust OOM score for shim") 194 } 195 return address, nil 196 } 197 198 func (s *service) Cleanup(ctx context.Context) (*taskAPI.DeleteResponse, error) { 199 path, err := os.Getwd() 200 if err != nil { 201 return nil, err 202 } 203 ns, err := namespaces.NamespaceRequired(ctx) 204 if err != nil { 205 return nil, err 206 } 207 runtime, err := runc.ReadRuntime(path) 208 if err != nil { 209 return nil, err 210 } 211 opts, err := runc.ReadOptions(path) 212 if err != nil { 213 return nil, err 214 } 215 root := process.RuncRoot 216 if opts != nil && opts.Root != "" { 217 root = opts.Root 218 } 219 220 r := process.NewRunc(root, path, ns, runtime, "", false) 221 if err := r.Delete(ctx, s.id, &runcC.DeleteOpts{ 222 Force: true, 223 }); err != nil { 224 logrus.WithError(err).Warn("failed to remove runc container") 225 } 226 if err := mount.UnmountAll(filepath.Join(path, "rootfs"), 0); err != nil { 227 logrus.WithError(err).Warn("failed to cleanup rootfs mount") 228 } 229 return &taskAPI.DeleteResponse{ 230 ExitedAt: time.Now(), 231 ExitStatus: 128 + uint32(unix.SIGKILL), 232 }, nil 233 } 234 235 // Create a new initial process and container with the underlying OCI runtime 236 func (s *service) Create(ctx context.Context, r *taskAPI.CreateTaskRequest) (_ *taskAPI.CreateTaskResponse, err error) { 237 s.mu.Lock() 238 defer s.mu.Unlock() 239 240 container, err := runc.NewContainer(ctx, s.platform, r) 241 if err != nil { 242 return nil, err 243 } 244 245 s.container = container 246 247 s.send(&eventstypes.TaskCreate{ 248 ContainerID: r.ID, 249 Bundle: r.Bundle, 250 Rootfs: r.Rootfs, 251 IO: &eventstypes.TaskIO{ 252 Stdin: r.Stdin, 253 Stdout: r.Stdout, 254 Stderr: r.Stderr, 255 Terminal: r.Terminal, 256 }, 257 Checkpoint: r.Checkpoint, 258 Pid: uint32(container.Pid()), 259 }) 260 261 return &taskAPI.CreateTaskResponse{ 262 Pid: uint32(container.Pid()), 263 }, nil 264 } 265 266 // Start a process 267 func (s *service) Start(ctx context.Context, r *taskAPI.StartRequest) (*taskAPI.StartResponse, error) { 268 container, err := s.getContainer() 269 if err != nil { 270 return nil, err 271 } 272 273 // hold the send lock so that the start events are sent before any exit events in the error case 274 s.eventSendMu.Lock() 275 p, err := container.Start(ctx, r) 276 if err != nil { 277 s.eventSendMu.Unlock() 278 return nil, errdefs.ToGRPC(err) 279 } 280 switch r.ExecID { 281 case "": 282 if cg, ok := container.Cgroup().(cgroups.Cgroup); ok { 283 if err := s.ep.Add(container.ID, cg); err != nil { 284 logrus.WithError(err).Error("add cg to OOM monitor") 285 } 286 } else { 287 logrus.WithError(errdefs.ErrNotImplemented).Error("add cg to OOM monitor") 288 } 289 s.send(&eventstypes.TaskStart{ 290 ContainerID: container.ID, 291 Pid: uint32(p.Pid()), 292 }) 293 default: 294 s.send(&eventstypes.TaskExecStarted{ 295 ContainerID: container.ID, 296 ExecID: r.ExecID, 297 Pid: uint32(p.Pid()), 298 }) 299 } 300 s.eventSendMu.Unlock() 301 return &taskAPI.StartResponse{ 302 Pid: uint32(p.Pid()), 303 }, nil 304 } 305 306 // Delete the initial process and container 307 func (s *service) Delete(ctx context.Context, r *taskAPI.DeleteRequest) (*taskAPI.DeleteResponse, error) { 308 container, err := s.getContainer() 309 if err != nil { 310 return nil, err 311 } 312 p, err := container.Delete(ctx, r) 313 if err != nil { 314 return nil, errdefs.ToGRPC(err) 315 } 316 // if we deleted our init task, close the platform and send the task delete event 317 if r.ExecID == "" { 318 if s.platform != nil { 319 s.platform.Close() 320 } 321 s.send(&eventstypes.TaskDelete{ 322 ContainerID: container.ID, 323 Pid: uint32(p.Pid()), 324 ExitStatus: uint32(p.ExitStatus()), 325 ExitedAt: p.ExitedAt(), 326 }) 327 } 328 return &taskAPI.DeleteResponse{ 329 ExitStatus: uint32(p.ExitStatus()), 330 ExitedAt: p.ExitedAt(), 331 Pid: uint32(p.Pid()), 332 }, nil 333 } 334 335 // Exec an additional process inside the container 336 func (s *service) Exec(ctx context.Context, r *taskAPI.ExecProcessRequest) (*ptypes.Empty, error) { 337 container, err := s.getContainer() 338 if err != nil { 339 return nil, err 340 } 341 ok, cancel := container.ReserveProcess(r.ExecID) 342 if !ok { 343 return nil, errdefs.ToGRPCf(errdefs.ErrAlreadyExists, "id %s", r.ExecID) 344 } 345 process, err := container.Exec(ctx, r) 346 if err != nil { 347 cancel() 348 return nil, errdefs.ToGRPC(err) 349 } 350 351 s.send(&eventstypes.TaskExecAdded{ 352 ContainerID: s.container.ID, 353 ExecID: process.ID(), 354 }) 355 return empty, nil 356 } 357 358 // ResizePty of a process 359 func (s *service) ResizePty(ctx context.Context, r *taskAPI.ResizePtyRequest) (*ptypes.Empty, error) { 360 container, err := s.getContainer() 361 if err != nil { 362 return nil, err 363 } 364 if err := container.ResizePty(ctx, r); err != nil { 365 return nil, errdefs.ToGRPC(err) 366 } 367 return empty, nil 368 } 369 370 // State returns runtime state information for a process 371 func (s *service) State(ctx context.Context, r *taskAPI.StateRequest) (*taskAPI.StateResponse, error) { 372 p, err := s.getProcess(r.ExecID) 373 if err != nil { 374 return nil, err 375 } 376 st, err := p.Status(ctx) 377 if err != nil { 378 return nil, err 379 } 380 status := task.StatusUnknown 381 switch st { 382 case "created": 383 status = task.StatusCreated 384 case "running": 385 status = task.StatusRunning 386 case "stopped": 387 status = task.StatusStopped 388 case "paused": 389 status = task.StatusPaused 390 case "pausing": 391 status = task.StatusPausing 392 } 393 sio := p.Stdio() 394 return &taskAPI.StateResponse{ 395 ID: p.ID(), 396 Bundle: s.container.Bundle, 397 Pid: uint32(p.Pid()), 398 Status: status, 399 Stdin: sio.Stdin, 400 Stdout: sio.Stdout, 401 Stderr: sio.Stderr, 402 Terminal: sio.Terminal, 403 ExitStatus: uint32(p.ExitStatus()), 404 ExitedAt: p.ExitedAt(), 405 }, nil 406 } 407 408 // Pause the container 409 func (s *service) Pause(ctx context.Context, r *taskAPI.PauseRequest) (*ptypes.Empty, error) { 410 container, err := s.getContainer() 411 if err != nil { 412 return nil, err 413 } 414 if err := container.Pause(ctx); err != nil { 415 return nil, errdefs.ToGRPC(err) 416 } 417 s.send(&eventstypes.TaskPaused{ 418 ContainerID: container.ID, 419 }) 420 return empty, nil 421 } 422 423 // Resume the container 424 func (s *service) Resume(ctx context.Context, r *taskAPI.ResumeRequest) (*ptypes.Empty, error) { 425 container, err := s.getContainer() 426 if err != nil { 427 return nil, err 428 } 429 if err := container.Resume(ctx); err != nil { 430 return nil, errdefs.ToGRPC(err) 431 } 432 s.send(&eventstypes.TaskResumed{ 433 ContainerID: container.ID, 434 }) 435 return empty, nil 436 } 437 438 // Kill a process with the provided signal 439 func (s *service) Kill(ctx context.Context, r *taskAPI.KillRequest) (*ptypes.Empty, error) { 440 container, err := s.getContainer() 441 if err != nil { 442 return nil, err 443 } 444 if err := container.Kill(ctx, r); err != nil { 445 return nil, errdefs.ToGRPC(err) 446 } 447 return empty, nil 448 } 449 450 // Pids returns all pids inside the container 451 func (s *service) Pids(ctx context.Context, r *taskAPI.PidsRequest) (*taskAPI.PidsResponse, error) { 452 container, err := s.getContainer() 453 if err != nil { 454 return nil, err 455 } 456 pids, err := s.getContainerPids(ctx, r.ID) 457 if err != nil { 458 return nil, errdefs.ToGRPC(err) 459 } 460 var processes []*task.ProcessInfo 461 for _, pid := range pids { 462 pInfo := task.ProcessInfo{ 463 Pid: pid, 464 } 465 for _, p := range container.ExecdProcesses() { 466 if p.Pid() == int(pid) { 467 d := &options.ProcessDetails{ 468 ExecID: p.ID(), 469 } 470 a, err := typeurl.MarshalAny(d) 471 if err != nil { 472 return nil, errors.Wrapf(err, "failed to marshal process %d info", pid) 473 } 474 pInfo.Info = a 475 break 476 } 477 } 478 processes = append(processes, &pInfo) 479 } 480 return &taskAPI.PidsResponse{ 481 Processes: processes, 482 }, nil 483 } 484 485 // CloseIO of a process 486 func (s *service) CloseIO(ctx context.Context, r *taskAPI.CloseIORequest) (*ptypes.Empty, error) { 487 container, err := s.getContainer() 488 if err != nil { 489 return nil, err 490 } 491 if err := container.CloseIO(ctx, r); err != nil { 492 return nil, err 493 } 494 return empty, nil 495 } 496 497 // Checkpoint the container 498 func (s *service) Checkpoint(ctx context.Context, r *taskAPI.CheckpointTaskRequest) (*ptypes.Empty, error) { 499 container, err := s.getContainer() 500 if err != nil { 501 return nil, err 502 } 503 if err := container.Checkpoint(ctx, r); err != nil { 504 return nil, errdefs.ToGRPC(err) 505 } 506 return empty, nil 507 } 508 509 // Update a running container 510 func (s *service) Update(ctx context.Context, r *taskAPI.UpdateTaskRequest) (*ptypes.Empty, error) { 511 container, err := s.getContainer() 512 if err != nil { 513 return nil, err 514 } 515 if err := container.Update(ctx, r); err != nil { 516 return nil, errdefs.ToGRPC(err) 517 } 518 return empty, nil 519 } 520 521 // Wait for a process to exit 522 func (s *service) Wait(ctx context.Context, r *taskAPI.WaitRequest) (*taskAPI.WaitResponse, error) { 523 container, err := s.getContainer() 524 if err != nil { 525 return nil, err 526 } 527 p, err := container.Process(r.ExecID) 528 if err != nil { 529 return nil, errdefs.ToGRPC(err) 530 } 531 p.Wait() 532 533 return &taskAPI.WaitResponse{ 534 ExitStatus: uint32(p.ExitStatus()), 535 ExitedAt: p.ExitedAt(), 536 }, nil 537 } 538 539 // Connect returns shim information such as the shim's pid 540 func (s *service) Connect(ctx context.Context, r *taskAPI.ConnectRequest) (*taskAPI.ConnectResponse, error) { 541 var pid int 542 if s.container != nil { 543 pid = s.container.Pid() 544 } 545 return &taskAPI.ConnectResponse{ 546 ShimPid: uint32(os.Getpid()), 547 TaskPid: uint32(pid), 548 }, nil 549 } 550 551 func (s *service) Shutdown(ctx context.Context, r *taskAPI.ShutdownRequest) (*ptypes.Empty, error) { 552 s.cancel() 553 close(s.events) 554 return empty, nil 555 } 556 557 func (s *service) Stats(ctx context.Context, r *taskAPI.StatsRequest) (*taskAPI.StatsResponse, error) { 558 cgx := s.container.Cgroup() 559 if cgx == nil { 560 return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "cgroup does not exist") 561 } 562 cg, ok := cgx.(cgroups.Cgroup) 563 if !ok { 564 return nil, errdefs.ToGRPCf(errdefs.ErrNotImplemented, "cgroup v2 not implemented for Stats") 565 } 566 if cg == nil { 567 return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "cgroup does not exist") 568 } 569 stats, err := cg.Stat(cgroups.IgnoreNotExist) 570 if err != nil { 571 return nil, err 572 } 573 data, err := typeurl.MarshalAny(stats) 574 if err != nil { 575 return nil, err 576 } 577 return &taskAPI.StatsResponse{ 578 Stats: data, 579 }, nil 580 } 581 582 func (s *service) processExits() { 583 for e := range s.ec { 584 s.checkProcesses(e) 585 } 586 } 587 588 func (s *service) send(evt interface{}) { 589 s.events <- evt 590 } 591 592 func (s *service) sendL(evt interface{}) { 593 s.eventSendMu.Lock() 594 s.events <- evt 595 s.eventSendMu.Unlock() 596 } 597 598 func (s *service) checkProcesses(e runcC.Exit) { 599 container, err := s.getContainer() 600 if err != nil { 601 return 602 } 603 604 for _, p := range container.All() { 605 if p.Pid() == e.Pid { 606 if runc.ShouldKillAllOnExit(s.context, container.Bundle) { 607 if ip, ok := p.(*process.Init); ok { 608 // Ensure all children are killed 609 if err := ip.KillAll(s.context); err != nil { 610 logrus.WithError(err).WithField("id", ip.ID()). 611 Error("failed to kill init's children") 612 } 613 } 614 } 615 p.SetExited(e.Status) 616 s.sendL(&eventstypes.TaskExit{ 617 ContainerID: container.ID, 618 ID: p.ID(), 619 Pid: uint32(e.Pid), 620 ExitStatus: uint32(e.Status), 621 ExitedAt: p.ExitedAt(), 622 }) 623 return 624 } 625 } 626 } 627 628 func (s *service) getContainerPids(ctx context.Context, id string) ([]uint32, error) { 629 p, err := s.container.Process("") 630 if err != nil { 631 return nil, errdefs.ToGRPC(err) 632 } 633 ps, err := p.(*process.Init).Runtime().Ps(ctx, id) 634 if err != nil { 635 return nil, err 636 } 637 pids := make([]uint32, 0, len(ps)) 638 for _, pid := range ps { 639 pids = append(pids, uint32(pid)) 640 } 641 return pids, nil 642 } 643 644 func (s *service) forward(ctx context.Context, publisher shim.Publisher) { 645 ns, _ := namespaces.Namespace(ctx) 646 ctx = namespaces.WithNamespace(context.Background(), ns) 647 for e := range s.events { 648 ctx, cancel := context.WithTimeout(ctx, 5*time.Second) 649 err := publisher.Publish(ctx, runc.GetTopic(e), e) 650 cancel() 651 if err != nil { 652 logrus.WithError(err).Error("post event") 653 } 654 } 655 publisher.Close() 656 } 657 658 func (s *service) getContainer() (*runc.Container, error) { 659 s.mu.Lock() 660 container := s.container 661 s.mu.Unlock() 662 if container == nil { 663 return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "container not created") 664 } 665 return container, nil 666 } 667 668 func (s *service) getProcess(execID string) (process.Process, error) { 669 container, err := s.getContainer() 670 if err != nil { 671 return nil, err 672 } 673 p, err := container.Process(execID) 674 if err != nil { 675 return nil, errdefs.ToGRPC(err) 676 } 677 return p, nil 678 } 679 680 // initialize a single epoll fd to manage our consoles. `initPlatform` should 681 // only be called once. 682 func (s *service) initPlatform() error { 683 if s.platform != nil { 684 return nil 685 } 686 p, err := runc.NewPlatform() 687 if err != nil { 688 return err 689 } 690 s.platform = p 691 return nil 692 }