github.com/containerd/Containerd@v1.4.13/runtime/v2/runc/container.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 runc
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"io/ioutil"
    25  	"os"
    26  	"path/filepath"
    27  	"sync"
    28  
    29  	"github.com/containerd/cgroups"
    30  	cgroupsv2 "github.com/containerd/cgroups/v2"
    31  	"github.com/containerd/console"
    32  	"github.com/containerd/containerd/errdefs"
    33  	"github.com/containerd/containerd/mount"
    34  	"github.com/containerd/containerd/namespaces"
    35  	"github.com/containerd/containerd/pkg/process"
    36  	"github.com/containerd/containerd/pkg/stdio"
    37  	"github.com/containerd/containerd/runtime/v2/runc/options"
    38  	"github.com/containerd/containerd/runtime/v2/task"
    39  	"github.com/containerd/typeurl"
    40  	"github.com/pkg/errors"
    41  	"github.com/sirupsen/logrus"
    42  )
    43  
    44  // NewContainer returns a new runc container
    45  func NewContainer(ctx context.Context, platform stdio.Platform, r *task.CreateTaskRequest) (_ *Container, retErr error) {
    46  	ns, err := namespaces.NamespaceRequired(ctx)
    47  	if err != nil {
    48  		return nil, errors.Wrap(err, "create namespace")
    49  	}
    50  
    51  	var opts options.Options
    52  	if r.Options != nil {
    53  		v, err := typeurl.UnmarshalAny(r.Options)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  		opts = *v.(*options.Options)
    58  	}
    59  
    60  	var mounts []process.Mount
    61  	for _, m := range r.Rootfs {
    62  		mounts = append(mounts, process.Mount{
    63  			Type:    m.Type,
    64  			Source:  m.Source,
    65  			Target:  m.Target,
    66  			Options: m.Options,
    67  		})
    68  	}
    69  
    70  	rootfs := ""
    71  	if len(mounts) > 0 {
    72  		rootfs = filepath.Join(r.Bundle, "rootfs")
    73  		if err := os.Mkdir(rootfs, 0711); err != nil && !os.IsExist(err) {
    74  			return nil, err
    75  		}
    76  	}
    77  
    78  	config := &process.CreateConfig{
    79  		ID:               r.ID,
    80  		Bundle:           r.Bundle,
    81  		Runtime:          opts.BinaryName,
    82  		Rootfs:           mounts,
    83  		Terminal:         r.Terminal,
    84  		Stdin:            r.Stdin,
    85  		Stdout:           r.Stdout,
    86  		Stderr:           r.Stderr,
    87  		Checkpoint:       r.Checkpoint,
    88  		ParentCheckpoint: r.ParentCheckpoint,
    89  		Options:          r.Options,
    90  	}
    91  
    92  	if err := WriteOptions(r.Bundle, opts); err != nil {
    93  		return nil, err
    94  	}
    95  	// For historical reason, we write opts.BinaryName as well as the entire opts
    96  	if err := WriteRuntime(r.Bundle, opts.BinaryName); err != nil {
    97  		return nil, err
    98  	}
    99  	defer func() {
   100  		if retErr != nil {
   101  			if err := mount.UnmountAll(rootfs, 0); err != nil {
   102  				logrus.WithError(err).Warn("failed to cleanup rootfs mount")
   103  			}
   104  		}
   105  	}()
   106  	for _, rm := range mounts {
   107  		m := &mount.Mount{
   108  			Type:    rm.Type,
   109  			Source:  rm.Source,
   110  			Options: rm.Options,
   111  		}
   112  		if err := m.Mount(rootfs); err != nil {
   113  			return nil, errors.Wrapf(err, "failed to mount rootfs component %v", m)
   114  		}
   115  	}
   116  
   117  	p, err := newInit(
   118  		ctx,
   119  		r.Bundle,
   120  		filepath.Join(r.Bundle, "work"),
   121  		ns,
   122  		platform,
   123  		config,
   124  		&opts,
   125  		rootfs,
   126  	)
   127  	if err != nil {
   128  		return nil, errdefs.ToGRPC(err)
   129  	}
   130  	if err := p.Create(ctx, config); err != nil {
   131  		return nil, errdefs.ToGRPC(err)
   132  	}
   133  	container := &Container{
   134  		ID:              r.ID,
   135  		Bundle:          r.Bundle,
   136  		process:         p,
   137  		processes:       make(map[string]process.Process),
   138  		reservedProcess: make(map[string]struct{}),
   139  	}
   140  	pid := p.Pid()
   141  	if pid > 0 {
   142  		var cg interface{}
   143  		if cgroups.Mode() == cgroups.Unified {
   144  			g, err := cgroupsv2.PidGroupPath(pid)
   145  			if err != nil {
   146  				logrus.WithError(err).Errorf("loading cgroup2 for %d", pid)
   147  				return container, nil
   148  			}
   149  			cg, err = cgroupsv2.LoadManager("/sys/fs/cgroup", g)
   150  			if err != nil {
   151  				logrus.WithError(err).Errorf("loading cgroup2 for %d", pid)
   152  			}
   153  		} else {
   154  			cg, err = cgroups.Load(cgroups.V1, cgroups.PidPath(pid))
   155  			if err != nil {
   156  				logrus.WithError(err).Errorf("loading cgroup for %d", pid)
   157  			}
   158  		}
   159  		container.cgroup = cg
   160  	}
   161  	return container, nil
   162  }
   163  
   164  const optionsFilename = "options.json"
   165  
   166  // ReadOptions reads the option information from the path.
   167  // When the file does not exist, ReadOptions returns nil without an error.
   168  func ReadOptions(path string) (*options.Options, error) {
   169  	filePath := filepath.Join(path, optionsFilename)
   170  	if _, err := os.Stat(filePath); err != nil {
   171  		if os.IsNotExist(err) {
   172  			return nil, nil
   173  		}
   174  		return nil, err
   175  	}
   176  
   177  	data, err := ioutil.ReadFile(filePath)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  	var opts options.Options
   182  	if err := json.Unmarshal(data, &opts); err != nil {
   183  		return nil, err
   184  	}
   185  	return &opts, nil
   186  }
   187  
   188  // WriteOptions writes the options information into the path
   189  func WriteOptions(path string, opts options.Options) error {
   190  	data, err := json.Marshal(opts)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	return ioutil.WriteFile(filepath.Join(path, optionsFilename), data, 0600)
   195  }
   196  
   197  // ReadRuntime reads the runtime information from the path
   198  func ReadRuntime(path string) (string, error) {
   199  	data, err := ioutil.ReadFile(filepath.Join(path, "runtime"))
   200  	if err != nil {
   201  		return "", err
   202  	}
   203  	return string(data), nil
   204  }
   205  
   206  // WriteRuntime writes the runtime information into the path
   207  func WriteRuntime(path, runtime string) error {
   208  	return ioutil.WriteFile(filepath.Join(path, "runtime"), []byte(runtime), 0600)
   209  }
   210  
   211  func newInit(ctx context.Context, path, workDir, namespace string, platform stdio.Platform,
   212  	r *process.CreateConfig, options *options.Options, rootfs string) (*process.Init, error) {
   213  	runtime := process.NewRunc(options.Root, path, namespace, options.BinaryName, options.CriuPath, options.SystemdCgroup)
   214  	p := process.New(r.ID, runtime, stdio.Stdio{
   215  		Stdin:    r.Stdin,
   216  		Stdout:   r.Stdout,
   217  		Stderr:   r.Stderr,
   218  		Terminal: r.Terminal,
   219  	})
   220  	p.Bundle = r.Bundle
   221  	p.Platform = platform
   222  	p.Rootfs = rootfs
   223  	p.WorkDir = workDir
   224  	p.IoUID = int(options.IoUid)
   225  	p.IoGID = int(options.IoGid)
   226  	p.NoPivotRoot = options.NoPivotRoot
   227  	p.NoNewKeyring = options.NoNewKeyring
   228  	p.CriuWorkPath = options.CriuWorkPath
   229  	if p.CriuWorkPath == "" {
   230  		// if criu work path not set, use container WorkDir
   231  		p.CriuWorkPath = p.WorkDir
   232  	}
   233  	return p, nil
   234  }
   235  
   236  // Container for operating on a runc container and its processes
   237  type Container struct {
   238  	mu sync.Mutex
   239  
   240  	// ID of the container
   241  	ID string
   242  	// Bundle path
   243  	Bundle string
   244  
   245  	// cgroup is either cgroups.Cgroup or *cgroupsv2.Manager
   246  	cgroup          interface{}
   247  	process         process.Process
   248  	processes       map[string]process.Process
   249  	reservedProcess map[string]struct{}
   250  }
   251  
   252  // All processes in the container
   253  func (c *Container) All() (o []process.Process) {
   254  	c.mu.Lock()
   255  	defer c.mu.Unlock()
   256  
   257  	for _, p := range c.processes {
   258  		o = append(o, p)
   259  	}
   260  	if c.process != nil {
   261  		o = append(o, c.process)
   262  	}
   263  	return o
   264  }
   265  
   266  // ExecdProcesses added to the container
   267  func (c *Container) ExecdProcesses() (o []process.Process) {
   268  	c.mu.Lock()
   269  	defer c.mu.Unlock()
   270  	for _, p := range c.processes {
   271  		o = append(o, p)
   272  	}
   273  	return o
   274  }
   275  
   276  // Pid of the main process of a container
   277  func (c *Container) Pid() int {
   278  	c.mu.Lock()
   279  	defer c.mu.Unlock()
   280  	return c.process.Pid()
   281  }
   282  
   283  // Cgroup of the container
   284  func (c *Container) Cgroup() interface{} {
   285  	c.mu.Lock()
   286  	defer c.mu.Unlock()
   287  	return c.cgroup
   288  }
   289  
   290  // CgroupSet sets the cgroup to the container
   291  func (c *Container) CgroupSet(cg interface{}) {
   292  	c.mu.Lock()
   293  	c.cgroup = cg
   294  	c.mu.Unlock()
   295  }
   296  
   297  // Process returns the process by id
   298  func (c *Container) Process(id string) (process.Process, error) {
   299  	c.mu.Lock()
   300  	defer c.mu.Unlock()
   301  	if id == "" {
   302  		if c.process == nil {
   303  			return nil, errors.Wrapf(errdefs.ErrFailedPrecondition, "container must be created")
   304  		}
   305  		return c.process, nil
   306  	}
   307  	p, ok := c.processes[id]
   308  	if !ok {
   309  		return nil, errors.Wrapf(errdefs.ErrNotFound, "process does not exist %s", id)
   310  	}
   311  	return p, nil
   312  }
   313  
   314  // ReserveProcess checks for the existence of an id and atomically
   315  // reserves the process id if it does not already exist
   316  //
   317  // Returns true if the process id was successfully reserved and a
   318  // cancel func to release the reservation
   319  func (c *Container) ReserveProcess(id string) (bool, func()) {
   320  	c.mu.Lock()
   321  	defer c.mu.Unlock()
   322  
   323  	if _, ok := c.processes[id]; ok {
   324  		return false, nil
   325  	}
   326  	if _, ok := c.reservedProcess[id]; ok {
   327  		return false, nil
   328  	}
   329  	c.reservedProcess[id] = struct{}{}
   330  	return true, func() {
   331  		c.mu.Lock()
   332  		defer c.mu.Unlock()
   333  		delete(c.reservedProcess, id)
   334  	}
   335  }
   336  
   337  // ProcessAdd adds a new process to the container
   338  func (c *Container) ProcessAdd(process process.Process) {
   339  	c.mu.Lock()
   340  	defer c.mu.Unlock()
   341  
   342  	delete(c.reservedProcess, process.ID())
   343  	c.processes[process.ID()] = process
   344  }
   345  
   346  // ProcessRemove removes the process by id from the container
   347  func (c *Container) ProcessRemove(id string) {
   348  	c.mu.Lock()
   349  	defer c.mu.Unlock()
   350  	delete(c.processes, id)
   351  }
   352  
   353  // Start a container process
   354  func (c *Container) Start(ctx context.Context, r *task.StartRequest) (process.Process, error) {
   355  	p, err := c.Process(r.ExecID)
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  	if err := p.Start(ctx); err != nil {
   360  		return nil, err
   361  	}
   362  	if c.Cgroup() == nil && p.Pid() > 0 {
   363  		var cg interface{}
   364  		if cgroups.Mode() == cgroups.Unified {
   365  			g, err := cgroupsv2.PidGroupPath(p.Pid())
   366  			if err != nil {
   367  				logrus.WithError(err).Errorf("loading cgroup2 for %d", p.Pid())
   368  			}
   369  			cg, err = cgroupsv2.LoadManager("/sys/fs/cgroup", g)
   370  			if err != nil {
   371  				logrus.WithError(err).Errorf("loading cgroup2 for %d", p.Pid())
   372  			}
   373  		} else {
   374  			cg, err = cgroups.Load(cgroups.V1, cgroups.PidPath(p.Pid()))
   375  			if err != nil {
   376  				logrus.WithError(err).Errorf("loading cgroup for %d", p.Pid())
   377  			}
   378  		}
   379  		c.cgroup = cg
   380  	}
   381  	return p, nil
   382  }
   383  
   384  // Delete the container or a process by id
   385  func (c *Container) Delete(ctx context.Context, r *task.DeleteRequest) (process.Process, error) {
   386  	p, err := c.Process(r.ExecID)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  	if err := p.Delete(ctx); err != nil {
   391  		return nil, err
   392  	}
   393  	if r.ExecID != "" {
   394  		c.ProcessRemove(r.ExecID)
   395  	}
   396  	return p, nil
   397  }
   398  
   399  // Exec an additional process
   400  func (c *Container) Exec(ctx context.Context, r *task.ExecProcessRequest) (process.Process, error) {
   401  	process, err := c.process.(*process.Init).Exec(ctx, c.Bundle, &process.ExecConfig{
   402  		ID:       r.ExecID,
   403  		Terminal: r.Terminal,
   404  		Stdin:    r.Stdin,
   405  		Stdout:   r.Stdout,
   406  		Stderr:   r.Stderr,
   407  		Spec:     r.Spec,
   408  	})
   409  	if err != nil {
   410  		return nil, err
   411  	}
   412  	c.ProcessAdd(process)
   413  	return process, nil
   414  }
   415  
   416  // Pause the container
   417  func (c *Container) Pause(ctx context.Context) error {
   418  	return c.process.(*process.Init).Pause(ctx)
   419  }
   420  
   421  // Resume the container
   422  func (c *Container) Resume(ctx context.Context) error {
   423  	return c.process.(*process.Init).Resume(ctx)
   424  }
   425  
   426  // ResizePty of a process
   427  func (c *Container) ResizePty(ctx context.Context, r *task.ResizePtyRequest) error {
   428  	p, err := c.Process(r.ExecID)
   429  	if err != nil {
   430  		return err
   431  	}
   432  	ws := console.WinSize{
   433  		Width:  uint16(r.Width),
   434  		Height: uint16(r.Height),
   435  	}
   436  	return p.Resize(ws)
   437  }
   438  
   439  // Kill a process
   440  func (c *Container) Kill(ctx context.Context, r *task.KillRequest) error {
   441  	p, err := c.Process(r.ExecID)
   442  	if err != nil {
   443  		return err
   444  	}
   445  	return p.Kill(ctx, r.Signal, r.All)
   446  }
   447  
   448  // CloseIO of a process
   449  func (c *Container) CloseIO(ctx context.Context, r *task.CloseIORequest) error {
   450  	p, err := c.Process(r.ExecID)
   451  	if err != nil {
   452  		return err
   453  	}
   454  	if stdin := p.Stdin(); stdin != nil {
   455  		if err := stdin.Close(); err != nil {
   456  			return errors.Wrap(err, "close stdin")
   457  		}
   458  	}
   459  	return nil
   460  }
   461  
   462  // Checkpoint the container
   463  func (c *Container) Checkpoint(ctx context.Context, r *task.CheckpointTaskRequest) error {
   464  	p, err := c.Process("")
   465  	if err != nil {
   466  		return err
   467  	}
   468  	var opts options.CheckpointOptions
   469  	if r.Options != nil {
   470  		v, err := typeurl.UnmarshalAny(r.Options)
   471  		if err != nil {
   472  			return err
   473  		}
   474  		opts = *v.(*options.CheckpointOptions)
   475  	}
   476  	return p.(*process.Init).Checkpoint(ctx, &process.CheckpointConfig{
   477  		Path:                     r.Path,
   478  		Exit:                     opts.Exit,
   479  		AllowOpenTCP:             opts.OpenTcp,
   480  		AllowExternalUnixSockets: opts.ExternalUnixSockets,
   481  		AllowTerminal:            opts.Terminal,
   482  		FileLocks:                opts.FileLocks,
   483  		EmptyNamespaces:          opts.EmptyNamespaces,
   484  		WorkDir:                  opts.WorkPath,
   485  	})
   486  }
   487  
   488  // Update the resource information of a running container
   489  func (c *Container) Update(ctx context.Context, r *task.UpdateTaskRequest) error {
   490  	p, err := c.Process("")
   491  	if err != nil {
   492  		return err
   493  	}
   494  	return p.(*process.Init).Update(ctx, r.Resources)
   495  }
   496  
   497  // HasPid returns true if the container owns a specific pid
   498  func (c *Container) HasPid(pid int) bool {
   499  	if c.Pid() == pid {
   500  		return true
   501  	}
   502  	for _, p := range c.All() {
   503  		if p.Pid() == pid {
   504  			return true
   505  		}
   506  	}
   507  	return false
   508  }