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