github.com/demonoid81/containerd@v1.3.4/runtime/v1/shim/service.go (about) 1 // +build !windows 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 shim 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "sync" 29 30 "github.com/containerd/console" 31 eventstypes "github.com/containerd/containerd/api/events" 32 "github.com/containerd/containerd/api/types/task" 33 "github.com/containerd/containerd/errdefs" 34 "github.com/containerd/containerd/events" 35 "github.com/containerd/containerd/log" 36 "github.com/containerd/containerd/mount" 37 "github.com/containerd/containerd/namespaces" 38 "github.com/containerd/containerd/pkg/process" 39 "github.com/containerd/containerd/pkg/stdio" 40 "github.com/containerd/containerd/runtime" 41 "github.com/containerd/containerd/runtime/linux/runctypes" 42 shimapi "github.com/containerd/containerd/runtime/v1/shim/v1" 43 "github.com/containerd/containerd/sys/reaper" 44 runc "github.com/containerd/go-runc" 45 "github.com/containerd/typeurl" 46 ptypes "github.com/gogo/protobuf/types" 47 specs "github.com/opencontainers/runtime-spec/specs-go" 48 "github.com/pkg/errors" 49 "github.com/sirupsen/logrus" 50 "google.golang.org/grpc/codes" 51 "google.golang.org/grpc/status" 52 ) 53 54 var ( 55 empty = &ptypes.Empty{} 56 bufPool = sync.Pool{ 57 New: func() interface{} { 58 buffer := make([]byte, 4096) 59 return &buffer 60 }, 61 } 62 ) 63 64 // Config contains shim specific configuration 65 type Config struct { 66 Path string 67 Namespace string 68 WorkDir string 69 Criu string 70 RuntimeRoot string 71 SystemdCgroup bool 72 } 73 74 // NewService returns a new shim service that can be used via GRPC 75 func NewService(config Config, publisher events.Publisher) (*Service, error) { 76 if config.Namespace == "" { 77 return nil, fmt.Errorf("shim namespace cannot be empty") 78 } 79 ctx := namespaces.WithNamespace(context.Background(), config.Namespace) 80 ctx = log.WithLogger(ctx, logrus.WithFields(logrus.Fields{ 81 "namespace": config.Namespace, 82 "path": config.Path, 83 "pid": os.Getpid(), 84 })) 85 s := &Service{ 86 config: config, 87 context: ctx, 88 processes: make(map[string]process.Process), 89 events: make(chan interface{}, 128), 90 ec: reaper.Default.Subscribe(), 91 } 92 go s.processExits() 93 if err := s.initPlatform(); err != nil { 94 return nil, errors.Wrap(err, "failed to initialized platform behavior") 95 } 96 go s.forward(publisher) 97 return s, nil 98 } 99 100 // Service is the shim implementation of a remote shim over GRPC 101 type Service struct { 102 mu sync.Mutex 103 104 config Config 105 context context.Context 106 processes map[string]process.Process 107 events chan interface{} 108 platform stdio.Platform 109 ec chan runc.Exit 110 111 // Filled by Create() 112 id string 113 bundle string 114 } 115 116 // Create a new initial process and container with the underlying OCI runtime 117 func (s *Service) Create(ctx context.Context, r *shimapi.CreateTaskRequest) (_ *shimapi.CreateTaskResponse, err error) { 118 var mounts []process.Mount 119 for _, m := range r.Rootfs { 120 mounts = append(mounts, process.Mount{ 121 Type: m.Type, 122 Source: m.Source, 123 Target: m.Target, 124 Options: m.Options, 125 }) 126 } 127 128 rootfs := "" 129 if len(mounts) > 0 { 130 rootfs = filepath.Join(r.Bundle, "rootfs") 131 if err := os.Mkdir(rootfs, 0711); err != nil && !os.IsExist(err) { 132 return nil, err 133 } 134 } 135 136 config := &process.CreateConfig{ 137 ID: r.ID, 138 Bundle: r.Bundle, 139 Runtime: r.Runtime, 140 Rootfs: mounts, 141 Terminal: r.Terminal, 142 Stdin: r.Stdin, 143 Stdout: r.Stdout, 144 Stderr: r.Stderr, 145 Checkpoint: r.Checkpoint, 146 ParentCheckpoint: r.ParentCheckpoint, 147 Options: r.Options, 148 } 149 defer func() { 150 if err != nil { 151 if err2 := mount.UnmountAll(rootfs, 0); err2 != nil { 152 log.G(ctx).WithError(err2).Warn("Failed to cleanup rootfs mount") 153 } 154 } 155 }() 156 for _, rm := range mounts { 157 m := &mount.Mount{ 158 Type: rm.Type, 159 Source: rm.Source, 160 Options: rm.Options, 161 } 162 if err := m.Mount(rootfs); err != nil { 163 return nil, errors.Wrapf(err, "failed to mount rootfs component %v", m) 164 } 165 } 166 167 s.mu.Lock() 168 defer s.mu.Unlock() 169 170 process, err := newInit( 171 ctx, 172 s.config.Path, 173 s.config.WorkDir, 174 s.config.RuntimeRoot, 175 s.config.Namespace, 176 s.config.Criu, 177 s.config.SystemdCgroup, 178 s.platform, 179 config, 180 rootfs, 181 ) 182 if err != nil { 183 return nil, errdefs.ToGRPC(err) 184 } 185 if err := process.Create(ctx, config); err != nil { 186 return nil, errdefs.ToGRPC(err) 187 } 188 // save the main task id and bundle to the shim for additional requests 189 s.id = r.ID 190 s.bundle = r.Bundle 191 pid := process.Pid() 192 s.processes[r.ID] = process 193 return &shimapi.CreateTaskResponse{ 194 Pid: uint32(pid), 195 }, nil 196 } 197 198 // Start a process 199 func (s *Service) Start(ctx context.Context, r *shimapi.StartRequest) (*shimapi.StartResponse, error) { 200 p, err := s.getExecProcess(r.ID) 201 if err != nil { 202 return nil, err 203 } 204 if err := p.Start(ctx); err != nil { 205 return nil, err 206 } 207 return &shimapi.StartResponse{ 208 ID: p.ID(), 209 Pid: uint32(p.Pid()), 210 }, nil 211 } 212 213 // Delete the initial process and container 214 func (s *Service) Delete(ctx context.Context, r *ptypes.Empty) (*shimapi.DeleteResponse, error) { 215 p, err := s.getInitProcess() 216 if err != nil { 217 return nil, err 218 } 219 if err := p.Delete(ctx); err != nil { 220 return nil, errdefs.ToGRPC(err) 221 } 222 s.mu.Lock() 223 delete(s.processes, s.id) 224 s.mu.Unlock() 225 s.platform.Close() 226 return &shimapi.DeleteResponse{ 227 ExitStatus: uint32(p.ExitStatus()), 228 ExitedAt: p.ExitedAt(), 229 Pid: uint32(p.Pid()), 230 }, nil 231 } 232 233 // DeleteProcess deletes an exec'd process 234 func (s *Service) DeleteProcess(ctx context.Context, r *shimapi.DeleteProcessRequest) (*shimapi.DeleteResponse, error) { 235 if r.ID == s.id { 236 return nil, status.Errorf(codes.InvalidArgument, "cannot delete init process with DeleteProcess") 237 } 238 p, err := s.getExecProcess(r.ID) 239 if err != nil { 240 return nil, err 241 } 242 if err := p.Delete(ctx); err != nil { 243 return nil, errdefs.ToGRPC(err) 244 } 245 s.mu.Lock() 246 delete(s.processes, r.ID) 247 s.mu.Unlock() 248 return &shimapi.DeleteResponse{ 249 ExitStatus: uint32(p.ExitStatus()), 250 ExitedAt: p.ExitedAt(), 251 Pid: uint32(p.Pid()), 252 }, nil 253 } 254 255 // Exec an additional process inside the container 256 func (s *Service) Exec(ctx context.Context, r *shimapi.ExecProcessRequest) (*ptypes.Empty, error) { 257 s.mu.Lock() 258 259 if p := s.processes[r.ID]; p != nil { 260 s.mu.Unlock() 261 return nil, errdefs.ToGRPCf(errdefs.ErrAlreadyExists, "id %s", r.ID) 262 } 263 264 p := s.processes[s.id] 265 s.mu.Unlock() 266 if p == nil { 267 return nil, errdefs.ToGRPCf(errdefs.ErrFailedPrecondition, "container must be created") 268 } 269 270 process, err := p.(*process.Init).Exec(ctx, s.config.Path, &process.ExecConfig{ 271 ID: r.ID, 272 Terminal: r.Terminal, 273 Stdin: r.Stdin, 274 Stdout: r.Stdout, 275 Stderr: r.Stderr, 276 Spec: r.Spec, 277 }) 278 if err != nil { 279 return nil, errdefs.ToGRPC(err) 280 } 281 s.mu.Lock() 282 s.processes[r.ID] = process 283 s.mu.Unlock() 284 return empty, nil 285 } 286 287 // ResizePty of a process 288 func (s *Service) ResizePty(ctx context.Context, r *shimapi.ResizePtyRequest) (*ptypes.Empty, error) { 289 if r.ID == "" { 290 return nil, errdefs.ToGRPCf(errdefs.ErrInvalidArgument, "id not provided") 291 } 292 ws := console.WinSize{ 293 Width: uint16(r.Width), 294 Height: uint16(r.Height), 295 } 296 s.mu.Lock() 297 p := s.processes[r.ID] 298 s.mu.Unlock() 299 if p == nil { 300 return nil, errors.Errorf("process does not exist %s", r.ID) 301 } 302 if err := p.Resize(ws); err != nil { 303 return nil, errdefs.ToGRPC(err) 304 } 305 return empty, nil 306 } 307 308 // State returns runtime state information for a process 309 func (s *Service) State(ctx context.Context, r *shimapi.StateRequest) (*shimapi.StateResponse, error) { 310 p, err := s.getExecProcess(r.ID) 311 if err != nil { 312 return nil, err 313 } 314 st, err := p.Status(ctx) 315 if err != nil { 316 return nil, err 317 } 318 status := task.StatusUnknown 319 switch st { 320 case "created": 321 status = task.StatusCreated 322 case "running": 323 status = task.StatusRunning 324 case "stopped": 325 status = task.StatusStopped 326 case "paused": 327 status = task.StatusPaused 328 case "pausing": 329 status = task.StatusPausing 330 } 331 sio := p.Stdio() 332 return &shimapi.StateResponse{ 333 ID: p.ID(), 334 Bundle: s.bundle, 335 Pid: uint32(p.Pid()), 336 Status: status, 337 Stdin: sio.Stdin, 338 Stdout: sio.Stdout, 339 Stderr: sio.Stderr, 340 Terminal: sio.Terminal, 341 ExitStatus: uint32(p.ExitStatus()), 342 ExitedAt: p.ExitedAt(), 343 }, nil 344 } 345 346 // Pause the container 347 func (s *Service) Pause(ctx context.Context, r *ptypes.Empty) (*ptypes.Empty, error) { 348 p, err := s.getInitProcess() 349 if err != nil { 350 return nil, err 351 } 352 if err := p.(*process.Init).Pause(ctx); err != nil { 353 return nil, err 354 } 355 return empty, nil 356 } 357 358 // Resume the container 359 func (s *Service) Resume(ctx context.Context, r *ptypes.Empty) (*ptypes.Empty, error) { 360 p, err := s.getInitProcess() 361 if err != nil { 362 return nil, err 363 } 364 if err := p.(*process.Init).Resume(ctx); err != nil { 365 return nil, err 366 } 367 return empty, nil 368 } 369 370 // Kill a process with the provided signal 371 func (s *Service) Kill(ctx context.Context, r *shimapi.KillRequest) (*ptypes.Empty, error) { 372 if r.ID == "" { 373 p, err := s.getInitProcess() 374 if err != nil { 375 return nil, err 376 } 377 if err := p.Kill(ctx, r.Signal, r.All); err != nil { 378 return nil, errdefs.ToGRPC(err) 379 } 380 return empty, nil 381 } 382 383 p, err := s.getExecProcess(r.ID) 384 if err != nil { 385 return nil, err 386 } 387 if err := p.Kill(ctx, r.Signal, r.All); err != nil { 388 return nil, errdefs.ToGRPC(err) 389 } 390 return empty, nil 391 } 392 393 // ListPids returns all pids inside the container 394 func (s *Service) ListPids(ctx context.Context, r *shimapi.ListPidsRequest) (*shimapi.ListPidsResponse, error) { 395 pids, err := s.getContainerPids(ctx, r.ID) 396 if err != nil { 397 return nil, errdefs.ToGRPC(err) 398 } 399 var processes []*task.ProcessInfo 400 for _, pid := range pids { 401 pInfo := task.ProcessInfo{ 402 Pid: pid, 403 } 404 for _, p := range s.processes { 405 if p.Pid() == int(pid) { 406 d := &runctypes.ProcessDetails{ 407 ExecID: p.ID(), 408 } 409 a, err := typeurl.MarshalAny(d) 410 if err != nil { 411 return nil, errors.Wrapf(err, "failed to marshal process %d info", pid) 412 } 413 pInfo.Info = a 414 break 415 } 416 } 417 processes = append(processes, &pInfo) 418 } 419 return &shimapi.ListPidsResponse{ 420 Processes: processes, 421 }, nil 422 } 423 424 // CloseIO of a process 425 func (s *Service) CloseIO(ctx context.Context, r *shimapi.CloseIORequest) (*ptypes.Empty, error) { 426 p, err := s.getExecProcess(r.ID) 427 if err != nil { 428 return nil, err 429 } 430 if stdin := p.Stdin(); stdin != nil { 431 if err := stdin.Close(); err != nil { 432 return nil, errors.Wrap(err, "close stdin") 433 } 434 } 435 return empty, nil 436 } 437 438 // Checkpoint the container 439 func (s *Service) Checkpoint(ctx context.Context, r *shimapi.CheckpointTaskRequest) (*ptypes.Empty, error) { 440 p, err := s.getInitProcess() 441 if err != nil { 442 return nil, err 443 } 444 var options runctypes.CheckpointOptions 445 if r.Options != nil { 446 v, err := typeurl.UnmarshalAny(r.Options) 447 if err != nil { 448 return nil, err 449 } 450 options = *v.(*runctypes.CheckpointOptions) 451 } 452 if err := p.(*process.Init).Checkpoint(ctx, &process.CheckpointConfig{ 453 Path: r.Path, 454 Exit: options.Exit, 455 AllowOpenTCP: options.OpenTcp, 456 AllowExternalUnixSockets: options.ExternalUnixSockets, 457 AllowTerminal: options.Terminal, 458 FileLocks: options.FileLocks, 459 EmptyNamespaces: options.EmptyNamespaces, 460 WorkDir: options.WorkPath, 461 }); err != nil { 462 return nil, errdefs.ToGRPC(err) 463 } 464 return empty, nil 465 } 466 467 // ShimInfo returns shim information such as the shim's pid 468 func (s *Service) ShimInfo(ctx context.Context, r *ptypes.Empty) (*shimapi.ShimInfoResponse, error) { 469 return &shimapi.ShimInfoResponse{ 470 ShimPid: uint32(os.Getpid()), 471 }, nil 472 } 473 474 // Update a running container 475 func (s *Service) Update(ctx context.Context, r *shimapi.UpdateTaskRequest) (*ptypes.Empty, error) { 476 p, err := s.getInitProcess() 477 if err != nil { 478 return nil, err 479 } 480 if err := p.(*process.Init).Update(ctx, r.Resources); err != nil { 481 return nil, errdefs.ToGRPC(err) 482 } 483 return empty, nil 484 } 485 486 // Wait for a process to exit 487 func (s *Service) Wait(ctx context.Context, r *shimapi.WaitRequest) (*shimapi.WaitResponse, error) { 488 p, err := s.getExecProcess(r.ID) 489 if err != nil { 490 return nil, err 491 } 492 p.Wait() 493 494 return &shimapi.WaitResponse{ 495 ExitStatus: uint32(p.ExitStatus()), 496 ExitedAt: p.ExitedAt(), 497 }, nil 498 } 499 500 func (s *Service) processExits() { 501 for e := range s.ec { 502 s.checkProcesses(e) 503 } 504 } 505 506 func (s *Service) allProcesses() []process.Process { 507 s.mu.Lock() 508 defer s.mu.Unlock() 509 510 res := make([]process.Process, 0, len(s.processes)) 511 for _, p := range s.processes { 512 res = append(res, p) 513 } 514 return res 515 } 516 517 func (s *Service) checkProcesses(e runc.Exit) { 518 for _, p := range s.allProcesses() { 519 if p.Pid() != e.Pid { 520 continue 521 } 522 523 if ip, ok := p.(*process.Init); ok { 524 shouldKillAll, err := shouldKillAllOnExit(s.bundle) 525 if err != nil { 526 log.G(s.context).WithError(err).Error("failed to check shouldKillAll") 527 } 528 529 // Ensure all children are killed 530 if shouldKillAll { 531 if err := ip.KillAll(s.context); err != nil { 532 log.G(s.context).WithError(err).WithField("id", ip.ID()). 533 Error("failed to kill init's children") 534 } 535 } 536 } 537 538 p.SetExited(e.Status) 539 s.events <- &eventstypes.TaskExit{ 540 ContainerID: s.id, 541 ID: p.ID(), 542 Pid: uint32(e.Pid), 543 ExitStatus: uint32(e.Status), 544 ExitedAt: p.ExitedAt(), 545 } 546 return 547 } 548 } 549 550 func shouldKillAllOnExit(bundlePath string) (bool, error) { 551 var bundleSpec specs.Spec 552 bundleConfigContents, err := ioutil.ReadFile(filepath.Join(bundlePath, "config.json")) 553 if err != nil { 554 return false, err 555 } 556 json.Unmarshal(bundleConfigContents, &bundleSpec) 557 558 if bundleSpec.Linux != nil { 559 for _, ns := range bundleSpec.Linux.Namespaces { 560 if ns.Type == specs.PIDNamespace && ns.Path == "" { 561 return false, nil 562 } 563 } 564 } 565 566 return true, nil 567 } 568 569 func (s *Service) getContainerPids(ctx context.Context, id string) ([]uint32, error) { 570 p, err := s.getInitProcess() 571 if err != nil { 572 return nil, err 573 } 574 575 ps, err := p.(*process.Init).Runtime().Ps(ctx, id) 576 if err != nil { 577 return nil, err 578 } 579 pids := make([]uint32, 0, len(ps)) 580 for _, pid := range ps { 581 pids = append(pids, uint32(pid)) 582 } 583 return pids, nil 584 } 585 586 func (s *Service) forward(publisher events.Publisher) { 587 for e := range s.events { 588 if err := publisher.Publish(s.context, getTopic(s.context, e), e); err != nil { 589 log.G(s.context).WithError(err).Error("post event") 590 } 591 } 592 } 593 594 // getInitProcess returns initial process 595 func (s *Service) getInitProcess() (process.Process, error) { 596 s.mu.Lock() 597 defer s.mu.Unlock() 598 599 p := s.processes[s.id] 600 if p == nil { 601 return nil, errdefs.ToGRPCf(errdefs.ErrFailedPrecondition, "container must be created") 602 } 603 return p, nil 604 } 605 606 // getExecProcess returns exec process 607 func (s *Service) getExecProcess(id string) (process.Process, error) { 608 s.mu.Lock() 609 defer s.mu.Unlock() 610 611 p := s.processes[id] 612 if p == nil { 613 return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "process %s does not exist", id) 614 } 615 return p, nil 616 } 617 618 func getTopic(ctx context.Context, e interface{}) string { 619 switch e.(type) { 620 case *eventstypes.TaskCreate: 621 return runtime.TaskCreateEventTopic 622 case *eventstypes.TaskStart: 623 return runtime.TaskStartEventTopic 624 case *eventstypes.TaskOOM: 625 return runtime.TaskOOMEventTopic 626 case *eventstypes.TaskExit: 627 return runtime.TaskExitEventTopic 628 case *eventstypes.TaskDelete: 629 return runtime.TaskDeleteEventTopic 630 case *eventstypes.TaskExecAdded: 631 return runtime.TaskExecAddedEventTopic 632 case *eventstypes.TaskExecStarted: 633 return runtime.TaskExecStartedEventTopic 634 case *eventstypes.TaskPaused: 635 return runtime.TaskPausedEventTopic 636 case *eventstypes.TaskResumed: 637 return runtime.TaskResumedEventTopic 638 case *eventstypes.TaskCheckpointed: 639 return runtime.TaskCheckpointedEventTopic 640 default: 641 logrus.Warnf("no topic for type %#v", e) 642 } 643 return runtime.TaskUnknownTopic 644 } 645 646 func newInit(ctx context.Context, path, workDir, runtimeRoot, namespace, criu string, systemdCgroup bool, platform stdio.Platform, r *process.CreateConfig, rootfs string) (*process.Init, error) { 647 var options runctypes.CreateOptions 648 if r.Options != nil { 649 v, err := typeurl.UnmarshalAny(r.Options) 650 if err != nil { 651 return nil, err 652 } 653 options = *v.(*runctypes.CreateOptions) 654 } 655 656 runtime := process.NewRunc(runtimeRoot, path, namespace, r.Runtime, criu, systemdCgroup) 657 p := process.New(r.ID, runtime, stdio.Stdio{ 658 Stdin: r.Stdin, 659 Stdout: r.Stdout, 660 Stderr: r.Stderr, 661 Terminal: r.Terminal, 662 }) 663 p.Bundle = r.Bundle 664 p.Platform = platform 665 p.Rootfs = rootfs 666 p.WorkDir = workDir 667 p.IoUID = int(options.IoUid) 668 p.IoGID = int(options.IoGid) 669 p.NoPivotRoot = options.NoPivotRoot 670 p.NoNewKeyring = options.NoNewKeyring 671 p.CriuWorkPath = options.CriuWorkPath 672 if p.CriuWorkPath == "" { 673 // if criu work path not set, use container WorkDir 674 p.CriuWorkPath = p.WorkDir 675 } 676 677 return p, nil 678 }