github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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) checkProcesses(e runc.Exit) { 507 var p process.Process 508 s.mu.Lock() 509 for _, proc := range s.processes { 510 if proc.Pid() == e.Pid { 511 p = proc 512 break 513 } 514 } 515 s.mu.Unlock() 516 if p == nil { 517 log.G(s.context).Debugf("process with id:%d wasn't found", e.Pid) 518 return 519 } 520 if ip, ok := p.(*process.Init); ok { 521 // Ensure all children are killed 522 if shouldKillAllOnExit(s.context, s.bundle) { 523 if err := ip.KillAll(s.context); err != nil { 524 log.G(s.context).WithError(err).WithField("id", ip.ID()). 525 Error("failed to kill init's children") 526 } 527 } 528 } 529 530 p.SetExited(e.Status) 531 s.events <- &eventstypes.TaskExit{ 532 ContainerID: s.id, 533 ID: p.ID(), 534 Pid: uint32(e.Pid), 535 ExitStatus: uint32(e.Status), 536 ExitedAt: p.ExitedAt(), 537 } 538 } 539 540 func shouldKillAllOnExit(ctx context.Context, bundlePath string) bool { 541 var bundleSpec specs.Spec 542 bundleConfigContents, err := ioutil.ReadFile(filepath.Join(bundlePath, "config.json")) 543 if err != nil { 544 log.G(ctx).WithError(err).Error("shouldKillAllOnExit: failed to read config.json") 545 return true 546 } 547 if err := json.Unmarshal(bundleConfigContents, &bundleSpec); err != nil { 548 log.G(ctx).WithError(err).Error("shouldKillAllOnExit: failed to unmarshal bundle json") 549 return true 550 } 551 if bundleSpec.Linux != nil { 552 for _, ns := range bundleSpec.Linux.Namespaces { 553 if ns.Type == specs.PIDNamespace && ns.Path == "" { 554 return false 555 } 556 } 557 } 558 return true 559 } 560 561 func (s *Service) getContainerPids(ctx context.Context, id string) ([]uint32, error) { 562 p, err := s.getInitProcess() 563 if err != nil { 564 return nil, err 565 } 566 567 ps, err := p.(*process.Init).Runtime().Ps(ctx, id) 568 if err != nil { 569 return nil, err 570 } 571 pids := make([]uint32, 0, len(ps)) 572 for _, pid := range ps { 573 pids = append(pids, uint32(pid)) 574 } 575 return pids, nil 576 } 577 578 func (s *Service) forward(publisher events.Publisher) { 579 for e := range s.events { 580 if err := publisher.Publish(s.context, getTopic(s.context, e), e); err != nil { 581 log.G(s.context).WithError(err).Error("post event") 582 } 583 } 584 } 585 586 // getInitProcess returns initial process 587 func (s *Service) getInitProcess() (process.Process, error) { 588 s.mu.Lock() 589 defer s.mu.Unlock() 590 591 p := s.processes[s.id] 592 if p == nil { 593 return nil, errdefs.ToGRPCf(errdefs.ErrFailedPrecondition, "container must be created") 594 } 595 return p, nil 596 } 597 598 // getExecProcess returns exec process 599 func (s *Service) getExecProcess(id string) (process.Process, error) { 600 s.mu.Lock() 601 defer s.mu.Unlock() 602 603 p := s.processes[id] 604 if p == nil { 605 return nil, errdefs.ToGRPCf(errdefs.ErrNotFound, "process %s does not exist", id) 606 } 607 return p, nil 608 } 609 610 func getTopic(ctx context.Context, e interface{}) string { 611 switch e.(type) { 612 case *eventstypes.TaskCreate: 613 return runtime.TaskCreateEventTopic 614 case *eventstypes.TaskStart: 615 return runtime.TaskStartEventTopic 616 case *eventstypes.TaskOOM: 617 return runtime.TaskOOMEventTopic 618 case *eventstypes.TaskExit: 619 return runtime.TaskExitEventTopic 620 case *eventstypes.TaskDelete: 621 return runtime.TaskDeleteEventTopic 622 case *eventstypes.TaskExecAdded: 623 return runtime.TaskExecAddedEventTopic 624 case *eventstypes.TaskExecStarted: 625 return runtime.TaskExecStartedEventTopic 626 case *eventstypes.TaskPaused: 627 return runtime.TaskPausedEventTopic 628 case *eventstypes.TaskResumed: 629 return runtime.TaskResumedEventTopic 630 case *eventstypes.TaskCheckpointed: 631 return runtime.TaskCheckpointedEventTopic 632 default: 633 logrus.Warnf("no topic for type %#v", e) 634 } 635 return runtime.TaskUnknownTopic 636 } 637 638 func newInit(ctx context.Context, path, workDir, runtimeRoot, namespace, criu string, systemdCgroup bool, platform stdio.Platform, r *process.CreateConfig, rootfs string) (*process.Init, error) { 639 var options runctypes.CreateOptions 640 if r.Options != nil { 641 v, err := typeurl.UnmarshalAny(r.Options) 642 if err != nil { 643 return nil, err 644 } 645 options = *v.(*runctypes.CreateOptions) 646 } 647 648 runtime := process.NewRunc(runtimeRoot, path, namespace, r.Runtime, criu, systemdCgroup) 649 p := process.New(r.ID, runtime, stdio.Stdio{ 650 Stdin: r.Stdin, 651 Stdout: r.Stdout, 652 Stderr: r.Stderr, 653 Terminal: r.Terminal, 654 }) 655 p.Bundle = r.Bundle 656 p.Platform = platform 657 p.Rootfs = rootfs 658 p.WorkDir = workDir 659 p.IoUID = int(options.IoUid) 660 p.IoGID = int(options.IoGid) 661 p.NoPivotRoot = options.NoPivotRoot 662 p.NoNewKeyring = options.NoNewKeyring 663 p.CriuWorkPath = options.CriuWorkPath 664 if p.CriuWorkPath == "" { 665 // if criu work path not set, use container WorkDir 666 p.CriuWorkPath = p.WorkDir 667 } 668 669 return p, nil 670 }