github.com/containerd/Containerd@v1.4.13/runtime/v2/shim.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v2 18 19 import ( 20 "context" 21 "io" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "time" 26 27 eventstypes "github.com/containerd/containerd/api/events" 28 "github.com/containerd/containerd/api/types" 29 tasktypes "github.com/containerd/containerd/api/types/task" 30 "github.com/containerd/containerd/errdefs" 31 "github.com/containerd/containerd/events/exchange" 32 "github.com/containerd/containerd/identifiers" 33 "github.com/containerd/containerd/log" 34 "github.com/containerd/containerd/namespaces" 35 "github.com/containerd/containerd/pkg/timeout" 36 "github.com/containerd/containerd/runtime" 37 client "github.com/containerd/containerd/runtime/v2/shim" 38 "github.com/containerd/containerd/runtime/v2/task" 39 "github.com/containerd/ttrpc" 40 ptypes "github.com/gogo/protobuf/types" 41 "github.com/pkg/errors" 42 "github.com/sirupsen/logrus" 43 ) 44 45 const ( 46 loadTimeout = "io.containerd.timeout.shim.load" 47 cleanupTimeout = "io.containerd.timeout.shim.cleanup" 48 shutdownTimeout = "io.containerd.timeout.shim.shutdown" 49 ) 50 51 func init() { 52 timeout.Set(loadTimeout, 5*time.Second) 53 timeout.Set(cleanupTimeout, 5*time.Second) 54 timeout.Set(shutdownTimeout, 3*time.Second) 55 } 56 57 func loadAddress(path string) (string, error) { 58 data, err := ioutil.ReadFile(path) 59 if err != nil { 60 return "", err 61 } 62 return string(data), nil 63 } 64 65 func loadShim(ctx context.Context, bundle *Bundle, events *exchange.Exchange, rt *runtime.TaskList, onClose func()) (_ *shim, err error) { 66 address, err := loadAddress(filepath.Join(bundle.Path, "address")) 67 if err != nil { 68 return nil, err 69 } 70 conn, err := client.Connect(address, client.AnonReconnectDialer) 71 if err != nil { 72 return nil, err 73 } 74 defer func() { 75 if err != nil { 76 conn.Close() 77 } 78 }() 79 shimCtx, cancelShimLog := context.WithCancel(ctx) 80 defer func() { 81 if err != nil { 82 cancelShimLog() 83 } 84 }() 85 f, err := openShimLog(shimCtx, bundle, client.AnonReconnectDialer) 86 if err != nil { 87 return nil, errors.Wrap(err, "open shim log pipe") 88 } 89 defer func() { 90 if err != nil { 91 f.Close() 92 } 93 }() 94 // open the log pipe and block until the writer is ready 95 // this helps with synchronization of the shim 96 // copy the shim's logs to containerd's output 97 go func() { 98 defer f.Close() 99 if _, err := io.Copy(os.Stderr, f); err != nil { 100 // When using a multi-container shim the 2nd to Nth container in the 101 // shim will not have a separate log pipe. Ignore the failure log 102 // message here when the shim connect times out. 103 if !errors.Is(err, os.ErrNotExist) { 104 log.G(ctx).WithError(err).Error("copy shim log") 105 } 106 } 107 }() 108 onCloseWithShimLog := func() { 109 onClose() 110 cancelShimLog() 111 f.Close() 112 } 113 client := ttrpc.NewClient(conn, ttrpc.WithOnClose(onCloseWithShimLog)) 114 defer func() { 115 if err != nil { 116 client.Close() 117 } 118 }() 119 s := &shim{ 120 client: client, 121 task: task.NewTaskClient(client), 122 bundle: bundle, 123 events: events, 124 rtTasks: rt, 125 } 126 ctx, cancel := timeout.WithContext(ctx, loadTimeout) 127 defer cancel() 128 if err := s.Connect(ctx); err != nil { 129 return nil, err 130 } 131 return s, nil 132 } 133 134 func cleanupAfterDeadShim(ctx context.Context, id, ns string, events *exchange.Exchange, binaryCall *binary) { 135 ctx = namespaces.WithNamespace(ctx, ns) 136 ctx, cancel := timeout.WithContext(ctx, cleanupTimeout) 137 defer cancel() 138 139 log.G(ctx).WithFields(logrus.Fields{ 140 "id": id, 141 "namespace": ns, 142 }).Warn("cleaning up after shim disconnected") 143 response, err := binaryCall.Delete(ctx) 144 if err != nil { 145 log.G(ctx).WithError(err).WithFields(logrus.Fields{ 146 "id": id, 147 "namespace": ns, 148 }).Warn("failed to clean up after shim disconnected") 149 } 150 151 var ( 152 pid uint32 153 exitStatus uint32 154 exitedAt time.Time 155 ) 156 if response != nil { 157 pid = response.Pid 158 exitStatus = response.Status 159 exitedAt = response.Timestamp 160 } else { 161 exitStatus = 255 162 exitedAt = time.Now() 163 } 164 events.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{ 165 ContainerID: id, 166 ID: id, 167 Pid: pid, 168 ExitStatus: exitStatus, 169 ExitedAt: exitedAt, 170 }) 171 172 events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{ 173 ContainerID: id, 174 Pid: pid, 175 ExitStatus: exitStatus, 176 ExitedAt: exitedAt, 177 }) 178 } 179 180 type shim struct { 181 bundle *Bundle 182 client *ttrpc.Client 183 task task.TaskService 184 taskPid int 185 events *exchange.Exchange 186 rtTasks *runtime.TaskList 187 } 188 189 func (s *shim) Connect(ctx context.Context) error { 190 response, err := s.task.Connect(ctx, &task.ConnectRequest{ 191 ID: s.ID(), 192 }) 193 if err != nil { 194 return err 195 } 196 s.taskPid = int(response.TaskPid) 197 return nil 198 } 199 200 func (s *shim) Shutdown(ctx context.Context) error { 201 _, err := s.task.Shutdown(ctx, &task.ShutdownRequest{ 202 ID: s.ID(), 203 }) 204 if err != nil && !errors.Is(err, ttrpc.ErrClosed) { 205 return errdefs.FromGRPC(err) 206 } 207 return nil 208 } 209 210 func (s *shim) waitShutdown(ctx context.Context) error { 211 ctx, cancel := timeout.WithContext(ctx, shutdownTimeout) 212 defer cancel() 213 return s.Shutdown(ctx) 214 } 215 216 // ID of the shim/task 217 func (s *shim) ID() string { 218 return s.bundle.ID 219 } 220 221 // PID of the task 222 func (s *shim) PID() uint32 { 223 return uint32(s.taskPid) 224 } 225 226 func (s *shim) Namespace() string { 227 return s.bundle.Namespace 228 } 229 230 func (s *shim) Close() error { 231 return s.client.Close() 232 } 233 234 func (s *shim) Delete(ctx context.Context) (*runtime.Exit, error) { 235 response, shimErr := s.task.Delete(ctx, &task.DeleteRequest{ 236 ID: s.ID(), 237 }) 238 if shimErr != nil { 239 log.G(ctx).WithField("id", s.ID()).WithError(shimErr).Debug("failed to delete task") 240 if !errors.Is(shimErr, ttrpc.ErrClosed) { 241 shimErr = errdefs.FromGRPC(shimErr) 242 if !errdefs.IsNotFound(shimErr) { 243 return nil, shimErr 244 } 245 } 246 } 247 // remove self from the runtime task list 248 // this seems dirty but it cleans up the API across runtimes, tasks, and the service 249 s.rtTasks.Delete(ctx, s.ID()) 250 if err := s.waitShutdown(ctx); err != nil { 251 log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to shutdown shim") 252 } 253 s.Close() 254 if err := s.bundle.Delete(); err != nil { 255 log.G(ctx).WithField("id", s.ID()).WithError(err).Error("failed to delete bundle") 256 } 257 if shimErr != nil { 258 return nil, shimErr 259 } 260 return &runtime.Exit{ 261 Status: response.ExitStatus, 262 Timestamp: response.ExitedAt, 263 Pid: response.Pid, 264 }, nil 265 } 266 267 func (s *shim) Create(ctx context.Context, opts runtime.CreateOpts) (runtime.Task, error) { 268 topts := opts.TaskOptions 269 if topts == nil { 270 topts = opts.RuntimeOptions 271 } 272 request := &task.CreateTaskRequest{ 273 ID: s.ID(), 274 Bundle: s.bundle.Path, 275 Stdin: opts.IO.Stdin, 276 Stdout: opts.IO.Stdout, 277 Stderr: opts.IO.Stderr, 278 Terminal: opts.IO.Terminal, 279 Checkpoint: opts.Checkpoint, 280 Options: topts, 281 } 282 for _, m := range opts.Rootfs { 283 request.Rootfs = append(request.Rootfs, &types.Mount{ 284 Type: m.Type, 285 Source: m.Source, 286 Options: m.Options, 287 }) 288 } 289 response, err := s.task.Create(ctx, request) 290 if err != nil { 291 return nil, errdefs.FromGRPC(err) 292 } 293 s.taskPid = int(response.Pid) 294 return s, nil 295 } 296 297 func (s *shim) Pause(ctx context.Context) error { 298 if _, err := s.task.Pause(ctx, &task.PauseRequest{ 299 ID: s.ID(), 300 }); err != nil { 301 return errdefs.FromGRPC(err) 302 } 303 return nil 304 } 305 306 func (s *shim) Resume(ctx context.Context) error { 307 if _, err := s.task.Resume(ctx, &task.ResumeRequest{ 308 ID: s.ID(), 309 }); err != nil { 310 return errdefs.FromGRPC(err) 311 } 312 return nil 313 } 314 315 func (s *shim) Start(ctx context.Context) error { 316 response, err := s.task.Start(ctx, &task.StartRequest{ 317 ID: s.ID(), 318 }) 319 if err != nil { 320 return errdefs.FromGRPC(err) 321 } 322 s.taskPid = int(response.Pid) 323 return nil 324 } 325 326 func (s *shim) Kill(ctx context.Context, signal uint32, all bool) error { 327 if _, err := s.task.Kill(ctx, &task.KillRequest{ 328 ID: s.ID(), 329 Signal: signal, 330 All: all, 331 }); err != nil { 332 return errdefs.FromGRPC(err) 333 } 334 return nil 335 } 336 337 func (s *shim) Exec(ctx context.Context, id string, opts runtime.ExecOpts) (runtime.Process, error) { 338 if err := identifiers.Validate(id); err != nil { 339 return nil, errors.Wrapf(err, "invalid exec id %s", id) 340 } 341 request := &task.ExecProcessRequest{ 342 ID: s.ID(), 343 ExecID: id, 344 Stdin: opts.IO.Stdin, 345 Stdout: opts.IO.Stdout, 346 Stderr: opts.IO.Stderr, 347 Terminal: opts.IO.Terminal, 348 Spec: opts.Spec, 349 } 350 if _, err := s.task.Exec(ctx, request); err != nil { 351 return nil, errdefs.FromGRPC(err) 352 } 353 return &process{ 354 id: id, 355 shim: s, 356 }, nil 357 } 358 359 func (s *shim) Pids(ctx context.Context) ([]runtime.ProcessInfo, error) { 360 resp, err := s.task.Pids(ctx, &task.PidsRequest{ 361 ID: s.ID(), 362 }) 363 if err != nil { 364 return nil, errdefs.FromGRPC(err) 365 } 366 var processList []runtime.ProcessInfo 367 for _, p := range resp.Processes { 368 processList = append(processList, runtime.ProcessInfo{ 369 Pid: p.Pid, 370 Info: p.Info, 371 }) 372 } 373 return processList, nil 374 } 375 376 func (s *shim) ResizePty(ctx context.Context, size runtime.ConsoleSize) error { 377 _, err := s.task.ResizePty(ctx, &task.ResizePtyRequest{ 378 ID: s.ID(), 379 Width: size.Width, 380 Height: size.Height, 381 }) 382 if err != nil { 383 return errdefs.FromGRPC(err) 384 } 385 return nil 386 } 387 388 func (s *shim) CloseIO(ctx context.Context) error { 389 _, err := s.task.CloseIO(ctx, &task.CloseIORequest{ 390 ID: s.ID(), 391 Stdin: true, 392 }) 393 if err != nil { 394 return errdefs.FromGRPC(err) 395 } 396 return nil 397 } 398 399 func (s *shim) Wait(ctx context.Context) (*runtime.Exit, error) { 400 response, err := s.task.Wait(ctx, &task.WaitRequest{ 401 ID: s.ID(), 402 }) 403 if err != nil { 404 return nil, errdefs.FromGRPC(err) 405 } 406 return &runtime.Exit{ 407 Pid: uint32(s.taskPid), 408 Timestamp: response.ExitedAt, 409 Status: response.ExitStatus, 410 }, nil 411 } 412 413 func (s *shim) Checkpoint(ctx context.Context, path string, options *ptypes.Any) error { 414 request := &task.CheckpointTaskRequest{ 415 ID: s.ID(), 416 Path: path, 417 Options: options, 418 } 419 if _, err := s.task.Checkpoint(ctx, request); err != nil { 420 return errdefs.FromGRPC(err) 421 } 422 return nil 423 } 424 425 func (s *shim) Update(ctx context.Context, resources *ptypes.Any) error { 426 if _, err := s.task.Update(ctx, &task.UpdateTaskRequest{ 427 ID: s.ID(), 428 Resources: resources, 429 }); err != nil { 430 return errdefs.FromGRPC(err) 431 } 432 return nil 433 } 434 435 func (s *shim) Stats(ctx context.Context) (*ptypes.Any, error) { 436 response, err := s.task.Stats(ctx, &task.StatsRequest{ 437 ID: s.ID(), 438 }) 439 if err != nil { 440 return nil, errdefs.FromGRPC(err) 441 } 442 return response.Stats, nil 443 } 444 445 func (s *shim) Process(ctx context.Context, id string) (runtime.Process, error) { 446 p := &process{ 447 id: id, 448 shim: s, 449 } 450 if _, err := p.State(ctx); err != nil { 451 return nil, err 452 } 453 return p, nil 454 } 455 456 func (s *shim) State(ctx context.Context) (runtime.State, error) { 457 response, err := s.task.State(ctx, &task.StateRequest{ 458 ID: s.ID(), 459 }) 460 if err != nil { 461 if !errors.Is(err, ttrpc.ErrClosed) { 462 return runtime.State{}, errdefs.FromGRPC(err) 463 } 464 return runtime.State{}, errdefs.ErrNotFound 465 } 466 var status runtime.Status 467 switch response.Status { 468 case tasktypes.StatusCreated: 469 status = runtime.CreatedStatus 470 case tasktypes.StatusRunning: 471 status = runtime.RunningStatus 472 case tasktypes.StatusStopped: 473 status = runtime.StoppedStatus 474 case tasktypes.StatusPaused: 475 status = runtime.PausedStatus 476 case tasktypes.StatusPausing: 477 status = runtime.PausingStatus 478 } 479 return runtime.State{ 480 Pid: response.Pid, 481 Status: status, 482 Stdin: response.Stdin, 483 Stdout: response.Stdout, 484 Stderr: response.Stderr, 485 Terminal: response.Terminal, 486 ExitStatus: response.ExitStatus, 487 ExitedAt: response.ExitedAt, 488 }, nil 489 }