github.com/containerd/Containerd@v1.4.13/runtime/v1/linux/runtime.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 linux
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"time"
    29  
    30  	eventstypes "github.com/containerd/containerd/api/events"
    31  	"github.com/containerd/containerd/api/types"
    32  	"github.com/containerd/containerd/containers"
    33  	"github.com/containerd/containerd/errdefs"
    34  	"github.com/containerd/containerd/events/exchange"
    35  	"github.com/containerd/containerd/identifiers"
    36  	"github.com/containerd/containerd/log"
    37  	"github.com/containerd/containerd/metadata"
    38  	"github.com/containerd/containerd/mount"
    39  	"github.com/containerd/containerd/namespaces"
    40  	"github.com/containerd/containerd/pkg/process"
    41  	"github.com/containerd/containerd/platforms"
    42  	"github.com/containerd/containerd/plugin"
    43  	"github.com/containerd/containerd/runtime"
    44  	"github.com/containerd/containerd/runtime/linux/runctypes"
    45  	v1 "github.com/containerd/containerd/runtime/v1"
    46  	shim "github.com/containerd/containerd/runtime/v1/shim/v1"
    47  	runc "github.com/containerd/go-runc"
    48  	"github.com/containerd/typeurl"
    49  	ptypes "github.com/gogo/protobuf/types"
    50  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    51  	"github.com/pkg/errors"
    52  	"github.com/sirupsen/logrus"
    53  	"golang.org/x/sys/unix"
    54  )
    55  
    56  var (
    57  	pluginID = fmt.Sprintf("%s.%s", plugin.RuntimePlugin, "linux")
    58  	empty    = &ptypes.Empty{}
    59  )
    60  
    61  const (
    62  	configFilename = "config.json"
    63  	defaultRuntime = "runc"
    64  	defaultShim    = "containerd-shim"
    65  
    66  	// cleanupTimeout is default timeout for cleanup operations
    67  	cleanupTimeout = 1 * time.Minute
    68  )
    69  
    70  func init() {
    71  	plugin.Register(&plugin.Registration{
    72  		Type:   plugin.RuntimePlugin,
    73  		ID:     "linux",
    74  		InitFn: New,
    75  		Requires: []plugin.Type{
    76  			plugin.MetadataPlugin,
    77  		},
    78  		Config: &Config{
    79  			Shim:    defaultShim,
    80  			Runtime: defaultRuntime,
    81  		},
    82  	})
    83  }
    84  
    85  var _ = (runtime.PlatformRuntime)(&Runtime{})
    86  
    87  // Config options for the runtime
    88  type Config struct {
    89  	// Shim is a path or name of binary implementing the Shim GRPC API
    90  	Shim string `toml:"shim"`
    91  	// Runtime is a path or name of an OCI runtime used by the shim
    92  	Runtime string `toml:"runtime"`
    93  	// RuntimeRoot is the path that shall be used by the OCI runtime for its data
    94  	RuntimeRoot string `toml:"runtime_root"`
    95  	// NoShim calls runc directly from within the pkg
    96  	NoShim bool `toml:"no_shim"`
    97  	// Debug enable debug on the shim
    98  	ShimDebug bool `toml:"shim_debug"`
    99  }
   100  
   101  // New returns a configured runtime
   102  func New(ic *plugin.InitContext) (interface{}, error) {
   103  	ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()}
   104  
   105  	if err := os.MkdirAll(ic.Root, 0711); err != nil {
   106  		return nil, err
   107  	}
   108  	if err := os.MkdirAll(ic.State, 0711); err != nil {
   109  		return nil, err
   110  	}
   111  	m, err := ic.Get(plugin.MetadataPlugin)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	cfg := ic.Config.(*Config)
   116  	r := &Runtime{
   117  		root:       ic.Root,
   118  		state:      ic.State,
   119  		tasks:      runtime.NewTaskList(),
   120  		containers: metadata.NewContainerStore(m.(*metadata.DB)),
   121  		address:    ic.Address,
   122  		events:     ic.Events,
   123  		config:     cfg,
   124  	}
   125  	tasks, err := r.restoreTasks(ic.Context)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	for _, t := range tasks {
   130  		if err := r.tasks.AddWithNamespace(t.namespace, t); err != nil {
   131  			return nil, err
   132  		}
   133  	}
   134  	return r, nil
   135  }
   136  
   137  // Runtime for a linux based system
   138  type Runtime struct {
   139  	root    string
   140  	state   string
   141  	address string
   142  
   143  	tasks      *runtime.TaskList
   144  	containers containers.Store
   145  	events     *exchange.Exchange
   146  
   147  	config *Config
   148  }
   149  
   150  // ID of the runtime
   151  func (r *Runtime) ID() string {
   152  	return pluginID
   153  }
   154  
   155  // Create a new task
   156  func (r *Runtime) Create(ctx context.Context, id string, opts runtime.CreateOpts) (_ runtime.Task, err error) {
   157  	namespace, err := namespaces.NamespaceRequired(ctx)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  
   162  	if err := identifiers.Validate(id); err != nil {
   163  		return nil, errors.Wrapf(err, "invalid task id")
   164  	}
   165  
   166  	ropts, err := r.getRuncOptions(ctx, id)
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	bundle, err := newBundle(id,
   172  		filepath.Join(r.state, namespace),
   173  		filepath.Join(r.root, namespace),
   174  		opts.Spec.Value)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  	defer func() {
   179  		if err != nil {
   180  			bundle.Delete()
   181  		}
   182  	}()
   183  
   184  	shimopt := ShimLocal(r.config, r.events)
   185  	if !r.config.NoShim {
   186  		var cgroup string
   187  		if opts.TaskOptions != nil {
   188  			v, err := typeurl.UnmarshalAny(opts.TaskOptions)
   189  			if err != nil {
   190  				return nil, err
   191  			}
   192  			cgroup = v.(*runctypes.CreateOptions).ShimCgroup
   193  		}
   194  		exitHandler := func() {
   195  			log.G(ctx).WithField("id", id).Info("shim reaped")
   196  
   197  			if _, err := r.tasks.Get(ctx, id); err != nil {
   198  				// Task was never started or was already successfully deleted
   199  				return
   200  			}
   201  
   202  			if err = r.cleanupAfterDeadShim(context.Background(), bundle, namespace, id); err != nil {
   203  				log.G(ctx).WithError(err).WithFields(logrus.Fields{
   204  					"id":        id,
   205  					"namespace": namespace,
   206  				}).Warn("failed to clean up after killed shim")
   207  			}
   208  		}
   209  		shimopt = ShimRemote(r.config, r.address, cgroup, exitHandler)
   210  	}
   211  
   212  	s, err := bundle.NewShimClient(ctx, namespace, shimopt, ropts)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	defer func() {
   217  		if err != nil {
   218  			deferCtx, deferCancel := context.WithTimeout(
   219  				namespaces.WithNamespace(context.TODO(), namespace), cleanupTimeout)
   220  			defer deferCancel()
   221  			if kerr := s.KillShim(deferCtx); kerr != nil {
   222  				log.G(ctx).WithError(err).Error("failed to kill shim")
   223  			}
   224  		}
   225  	}()
   226  
   227  	rt := r.config.Runtime
   228  	if ropts != nil && ropts.Runtime != "" {
   229  		rt = ropts.Runtime
   230  	}
   231  	sopts := &shim.CreateTaskRequest{
   232  		ID:         id,
   233  		Bundle:     bundle.path,
   234  		Runtime:    rt,
   235  		Stdin:      opts.IO.Stdin,
   236  		Stdout:     opts.IO.Stdout,
   237  		Stderr:     opts.IO.Stderr,
   238  		Terminal:   opts.IO.Terminal,
   239  		Checkpoint: opts.Checkpoint,
   240  		Options:    opts.TaskOptions,
   241  	}
   242  	for _, m := range opts.Rootfs {
   243  		sopts.Rootfs = append(sopts.Rootfs, &types.Mount{
   244  			Type:    m.Type,
   245  			Source:  m.Source,
   246  			Options: m.Options,
   247  		})
   248  	}
   249  	cr, err := s.Create(ctx, sopts)
   250  	if err != nil {
   251  		return nil, errdefs.FromGRPC(err)
   252  	}
   253  	t, err := newTask(id, namespace, int(cr.Pid), s, r.events, r.tasks, bundle)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  	if err := r.tasks.Add(ctx, t); err != nil {
   258  		return nil, err
   259  	}
   260  	r.events.Publish(ctx, runtime.TaskCreateEventTopic, &eventstypes.TaskCreate{
   261  		ContainerID: sopts.ID,
   262  		Bundle:      sopts.Bundle,
   263  		Rootfs:      sopts.Rootfs,
   264  		IO: &eventstypes.TaskIO{
   265  			Stdin:    sopts.Stdin,
   266  			Stdout:   sopts.Stdout,
   267  			Stderr:   sopts.Stderr,
   268  			Terminal: sopts.Terminal,
   269  		},
   270  		Checkpoint: sopts.Checkpoint,
   271  		Pid:        uint32(t.pid),
   272  	})
   273  
   274  	return t, nil
   275  }
   276  
   277  // Tasks returns all tasks known to the runtime
   278  func (r *Runtime) Tasks(ctx context.Context, all bool) ([]runtime.Task, error) {
   279  	return r.tasks.GetAll(ctx, all)
   280  }
   281  
   282  func (r *Runtime) restoreTasks(ctx context.Context) ([]*Task, error) {
   283  	dir, err := ioutil.ReadDir(r.state)
   284  	if err != nil {
   285  		return nil, err
   286  	}
   287  	var o []*Task
   288  	for _, namespace := range dir {
   289  		if !namespace.IsDir() {
   290  			continue
   291  		}
   292  		name := namespace.Name()
   293  		// skip hidden directories
   294  		if len(name) > 0 && name[0] == '.' {
   295  			continue
   296  		}
   297  		log.G(ctx).WithField("namespace", name).Debug("loading tasks in namespace")
   298  		tasks, err := r.loadTasks(ctx, name)
   299  		if err != nil {
   300  			return nil, err
   301  		}
   302  		o = append(o, tasks...)
   303  	}
   304  	return o, nil
   305  }
   306  
   307  // Get a specific task by task id
   308  func (r *Runtime) Get(ctx context.Context, id string) (runtime.Task, error) {
   309  	return r.tasks.Get(ctx, id)
   310  }
   311  
   312  // Add a runtime task
   313  func (r *Runtime) Add(ctx context.Context, task runtime.Task) error {
   314  	return r.tasks.Add(ctx, task)
   315  }
   316  
   317  // Delete a runtime task
   318  func (r *Runtime) Delete(ctx context.Context, id string) {
   319  	r.tasks.Delete(ctx, id)
   320  }
   321  
   322  func (r *Runtime) loadTasks(ctx context.Context, ns string) ([]*Task, error) {
   323  	dir, err := ioutil.ReadDir(filepath.Join(r.state, ns))
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	var o []*Task
   328  	for _, path := range dir {
   329  		if !path.IsDir() {
   330  			continue
   331  		}
   332  		id := path.Name()
   333  		// skip hidden directories
   334  		if len(id) > 0 && id[0] == '.' {
   335  			continue
   336  		}
   337  		bundle := loadBundle(
   338  			id,
   339  			filepath.Join(r.state, ns, id),
   340  			filepath.Join(r.root, ns, id),
   341  		)
   342  		ctx = namespaces.WithNamespace(ctx, ns)
   343  		pid, _ := runc.ReadPidFile(filepath.Join(bundle.path, process.InitPidFile))
   344  		shimExit := make(chan struct{})
   345  		s, err := bundle.NewShimClient(ctx, ns, ShimConnect(r.config, func() {
   346  			defer close(shimExit)
   347  			if _, err := r.tasks.Get(ctx, id); err != nil {
   348  				// Task was never started or was already successfully deleted
   349  				return
   350  			}
   351  
   352  			if err := r.cleanupAfterDeadShim(ctx, bundle, ns, id); err != nil {
   353  				log.G(ctx).WithError(err).WithField("bundle", bundle.path).
   354  					Error("cleaning up after dead shim")
   355  			}
   356  		}), nil)
   357  		if err != nil {
   358  			log.G(ctx).WithError(err).WithFields(logrus.Fields{
   359  				"id":        id,
   360  				"namespace": ns,
   361  			}).Error("connecting to shim")
   362  			err := r.cleanupAfterDeadShim(ctx, bundle, ns, id)
   363  			if err != nil {
   364  				log.G(ctx).WithError(err).WithField("bundle", bundle.path).
   365  					Error("cleaning up after dead shim")
   366  			}
   367  			continue
   368  		}
   369  
   370  		logDirPath := filepath.Join(r.root, ns, id)
   371  
   372  		copyAndClose := func(dst io.Writer, src io.ReadWriteCloser) {
   373  			copyDone := make(chan struct{})
   374  			go func() {
   375  				io.Copy(dst, src)
   376  				close(copyDone)
   377  			}()
   378  			select {
   379  			case <-shimExit:
   380  			case <-copyDone:
   381  			}
   382  			src.Close()
   383  		}
   384  		shimStdoutLog, err := v1.OpenShimStdoutLog(ctx, logDirPath)
   385  		if err != nil {
   386  			log.G(ctx).WithError(err).WithFields(logrus.Fields{
   387  				"id":         id,
   388  				"namespace":  ns,
   389  				"logDirPath": logDirPath,
   390  			}).Error("opening shim stdout log pipe")
   391  			continue
   392  		}
   393  		if r.config.ShimDebug {
   394  			go copyAndClose(os.Stdout, shimStdoutLog)
   395  		} else {
   396  			go copyAndClose(ioutil.Discard, shimStdoutLog)
   397  		}
   398  
   399  		shimStderrLog, err := v1.OpenShimStderrLog(ctx, logDirPath)
   400  		if err != nil {
   401  			log.G(ctx).WithError(err).WithFields(logrus.Fields{
   402  				"id":         id,
   403  				"namespace":  ns,
   404  				"logDirPath": logDirPath,
   405  			}).Error("opening shim stderr log pipe")
   406  			continue
   407  		}
   408  		if r.config.ShimDebug {
   409  			go copyAndClose(os.Stderr, shimStderrLog)
   410  		} else {
   411  			go copyAndClose(ioutil.Discard, shimStderrLog)
   412  		}
   413  
   414  		t, err := newTask(id, ns, pid, s, r.events, r.tasks, bundle)
   415  		if err != nil {
   416  			log.G(ctx).WithError(err).Error("loading task type")
   417  			continue
   418  		}
   419  		o = append(o, t)
   420  	}
   421  	return o, nil
   422  }
   423  
   424  func (r *Runtime) cleanupAfterDeadShim(ctx context.Context, bundle *bundle, ns, id string) error {
   425  	log.G(ctx).WithFields(logrus.Fields{
   426  		"id":        id,
   427  		"namespace": ns,
   428  	}).Warn("cleaning up after shim dead")
   429  
   430  	pid, _ := runc.ReadPidFile(filepath.Join(bundle.path, process.InitPidFile))
   431  	ctx = namespaces.WithNamespace(ctx, ns)
   432  	if err := r.terminate(ctx, bundle, ns, id); err != nil {
   433  		if r.config.ShimDebug {
   434  			return errors.Wrap(err, "failed to terminate task, leaving bundle for debugging")
   435  		}
   436  		log.G(ctx).WithError(err).Warn("failed to terminate task")
   437  	}
   438  
   439  	// Notify Client
   440  	exitedAt := time.Now().UTC()
   441  	r.events.Publish(ctx, runtime.TaskExitEventTopic, &eventstypes.TaskExit{
   442  		ContainerID: id,
   443  		ID:          id,
   444  		Pid:         uint32(pid),
   445  		ExitStatus:  128 + uint32(unix.SIGKILL),
   446  		ExitedAt:    exitedAt,
   447  	})
   448  
   449  	r.tasks.Delete(ctx, id)
   450  	if err := bundle.Delete(); err != nil {
   451  		log.G(ctx).WithError(err).Error("delete bundle")
   452  	}
   453  	// kill shim
   454  	if shimPid, err := runc.ReadPidFile(filepath.Join(bundle.path, "shim.pid")); err == nil && shimPid > 0 {
   455  		unix.Kill(shimPid, unix.SIGKILL)
   456  	}
   457  
   458  	r.events.Publish(ctx, runtime.TaskDeleteEventTopic, &eventstypes.TaskDelete{
   459  		ContainerID: id,
   460  		Pid:         uint32(pid),
   461  		ExitStatus:  128 + uint32(unix.SIGKILL),
   462  		ExitedAt:    exitedAt,
   463  	})
   464  
   465  	return nil
   466  }
   467  
   468  func (r *Runtime) terminate(ctx context.Context, bundle *bundle, ns, id string) error {
   469  	rt, err := r.getRuntime(ctx, ns, id)
   470  	if err != nil {
   471  		return err
   472  	}
   473  	if err := rt.Delete(ctx, id, &runc.DeleteOpts{
   474  		Force: true,
   475  	}); err != nil {
   476  		log.G(ctx).WithError(err).Warnf("delete runtime state %s", id)
   477  	}
   478  	if err := mount.Unmount(filepath.Join(bundle.path, "rootfs"), 0); err != nil {
   479  		log.G(ctx).WithError(err).WithFields(logrus.Fields{
   480  			"path": bundle.path,
   481  			"id":   id,
   482  		}).Warnf("unmount task rootfs")
   483  	}
   484  	return nil
   485  }
   486  
   487  func (r *Runtime) getRuntime(ctx context.Context, ns, id string) (*runc.Runc, error) {
   488  	ropts, err := r.getRuncOptions(ctx, id)
   489  	if err != nil {
   490  		return nil, err
   491  	}
   492  
   493  	var (
   494  		cmd  = r.config.Runtime
   495  		root = process.RuncRoot
   496  	)
   497  	if ropts != nil {
   498  		if ropts.Runtime != "" {
   499  			cmd = ropts.Runtime
   500  		}
   501  		if ropts.RuntimeRoot != "" {
   502  			root = ropts.RuntimeRoot
   503  		}
   504  	}
   505  
   506  	return &runc.Runc{
   507  		Command:      cmd,
   508  		LogFormat:    runc.JSON,
   509  		PdeathSignal: unix.SIGKILL,
   510  		Root:         filepath.Join(root, ns),
   511  		Debug:        r.config.ShimDebug,
   512  	}, nil
   513  }
   514  
   515  func (r *Runtime) getRuncOptions(ctx context.Context, id string) (*runctypes.RuncOptions, error) {
   516  	container, err := r.containers.Get(ctx, id)
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	if container.Runtime.Options != nil {
   522  		v, err := typeurl.UnmarshalAny(container.Runtime.Options)
   523  		if err != nil {
   524  			return nil, err
   525  		}
   526  		ropts, ok := v.(*runctypes.RuncOptions)
   527  		if !ok {
   528  			return nil, errors.New("invalid runtime options format")
   529  		}
   530  
   531  		return ropts, nil
   532  	}
   533  	return &runctypes.RuncOptions{}, nil
   534  }