github.com/demonoid81/containerd@v1.3.4/cmd/containerd/command/main.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 command
    18  
    19  import (
    20  	gocontext "context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net"
    24  	"os"
    25  	"os/signal"
    26  	"path/filepath"
    27  	"runtime"
    28  	"time"
    29  
    30  	"github.com/containerd/containerd/errdefs"
    31  	"github.com/containerd/containerd/log"
    32  	"github.com/containerd/containerd/mount"
    33  	"github.com/containerd/containerd/services/server"
    34  	srvconfig "github.com/containerd/containerd/services/server/config"
    35  	"github.com/containerd/containerd/sys"
    36  	"github.com/containerd/containerd/version"
    37  	"github.com/pkg/errors"
    38  	"github.com/sirupsen/logrus"
    39  	"github.com/urfave/cli"
    40  	"google.golang.org/grpc/grpclog"
    41  )
    42  
    43  const usage = `
    44                      __        _                     __
    45    _________  ____  / /_____ _(_)___  ___  _________/ /
    46   / ___/ __ \/ __ \/ __/ __ ` + "`" + `/ / __ \/ _ \/ ___/ __  /
    47  / /__/ /_/ / / / / /_/ /_/ / / / / /  __/ /  / /_/ /
    48  \___/\____/_/ /_/\__/\__,_/_/_/ /_/\___/_/   \__,_/
    49  
    50  high performance container runtime
    51  `
    52  
    53  func init() {
    54  	logrus.SetFormatter(&logrus.TextFormatter{
    55  		TimestampFormat: log.RFC3339NanoFixed,
    56  		FullTimestamp:   true,
    57  	})
    58  
    59  	// Discard grpc logs so that they don't mess with our stdio
    60  	grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
    61  
    62  	cli.VersionPrinter = func(c *cli.Context) {
    63  		fmt.Println(c.App.Name, version.Package, c.App.Version, version.Revision)
    64  	}
    65  }
    66  
    67  // App returns a *cli.App instance.
    68  func App() *cli.App {
    69  	app := cli.NewApp()
    70  	app.Name = "containerd"
    71  	app.Version = version.Version
    72  	app.Usage = usage
    73  	app.Description = `
    74  containerd is a high performance container runtime whose daemon can be started
    75  by using this command. If none of the *config*, *publish*, or *help* commands
    76  are specified, the default action of the **containerd** command is to start the
    77  containerd daemon in the foreground.
    78  
    79  
    80  A default configuration is used if no TOML configuration is specified or located
    81  at the default file location. The *containerd config* command can be used to
    82  generate the default configuration for containerd. The output of that command
    83  can be used and modified as necessary as a custom configuration.`
    84  	app.Flags = []cli.Flag{
    85  		cli.StringFlag{
    86  			Name:  "config,c",
    87  			Usage: "path to the configuration file",
    88  			Value: defaultConfigPath,
    89  		},
    90  		cli.StringFlag{
    91  			Name:  "log-level,l",
    92  			Usage: "set the logging level [trace, debug, info, warn, error, fatal, panic]",
    93  		},
    94  		cli.StringFlag{
    95  			Name:  "address,a",
    96  			Usage: "address for containerd's GRPC server",
    97  		},
    98  		cli.StringFlag{
    99  			Name:  "root",
   100  			Usage: "containerd root directory",
   101  		},
   102  		cli.StringFlag{
   103  			Name:  "state",
   104  			Usage: "containerd state directory",
   105  		},
   106  	}
   107  	app.Flags = append(app.Flags, serviceFlags()...)
   108  	app.Commands = []cli.Command{
   109  		configCommand,
   110  		publishCommand,
   111  		ociHook,
   112  	}
   113  	app.Action = func(context *cli.Context) error {
   114  		var (
   115  			start   = time.Now()
   116  			signals = make(chan os.Signal, 2048)
   117  			serverC = make(chan *server.Server, 1)
   118  			ctx     = gocontext.Background()
   119  			config  = defaultConfig()
   120  		)
   121  
   122  		if err := srvconfig.LoadConfig(context.GlobalString("config"), config); err != nil && !os.IsNotExist(err) {
   123  			return err
   124  		}
   125  
   126  		// Apply flags to the config
   127  		if err := applyFlags(context, config); err != nil {
   128  			return err
   129  		}
   130  
   131  		// Make sure top-level directories are created early.
   132  		if err := server.CreateTopLevelDirectories(config); err != nil {
   133  			return err
   134  		}
   135  
   136  		// Stop if we are registering or unregistering against Windows SCM.
   137  		stop, err := registerUnregisterService(config.Root)
   138  		if err != nil {
   139  			logrus.Fatal(err)
   140  		}
   141  		if stop {
   142  			return nil
   143  		}
   144  
   145  		done := handleSignals(ctx, signals, serverC)
   146  		// start the signal handler as soon as we can to make sure that
   147  		// we don't miss any signals during boot
   148  		signal.Notify(signals, handledSignals...)
   149  
   150  		// cleanup temp mounts
   151  		if err := mount.SetTempMountLocation(filepath.Join(config.Root, "tmpmounts")); err != nil {
   152  			return errors.Wrap(err, "creating temp mount location")
   153  		}
   154  		// unmount all temp mounts on boot for the server
   155  		warnings, err := mount.CleanupTempMounts(0)
   156  		if err != nil {
   157  			log.G(ctx).WithError(err).Error("unmounting temp mounts")
   158  		}
   159  		for _, w := range warnings {
   160  			log.G(ctx).WithError(w).Warn("cleanup temp mount")
   161  		}
   162  
   163  		if config.GRPC.Address == "" {
   164  			return errors.Wrap(errdefs.ErrInvalidArgument, "grpc address cannot be empty")
   165  		}
   166  		if config.TTRPC.Address == "" {
   167  			// If TTRPC was not explicitly configured, use defaults based on GRPC.
   168  			config.TTRPC.Address = fmt.Sprintf("%s.ttrpc", config.GRPC.Address)
   169  			config.TTRPC.UID = config.GRPC.UID
   170  			config.TTRPC.GID = config.GRPC.GID
   171  		}
   172  		log.G(ctx).WithFields(logrus.Fields{
   173  			"version":  version.Version,
   174  			"revision": version.Revision,
   175  		}).Info("starting containerd")
   176  
   177  		server, err := server.New(ctx, config)
   178  		if err != nil {
   179  			return err
   180  		}
   181  
   182  		// Launch as a Windows Service if necessary
   183  		if err := launchService(server, done); err != nil {
   184  			logrus.Fatal(err)
   185  		}
   186  
   187  		serverC <- server
   188  
   189  		if config.Debug.Address != "" {
   190  			var l net.Listener
   191  			if filepath.IsAbs(config.Debug.Address) {
   192  				if l, err = sys.GetLocalListener(config.Debug.Address, config.Debug.UID, config.Debug.GID); err != nil {
   193  					return errors.Wrapf(err, "failed to get listener for debug endpoint")
   194  				}
   195  			} else {
   196  				if l, err = net.Listen("tcp", config.Debug.Address); err != nil {
   197  					return errors.Wrapf(err, "failed to get listener for debug endpoint")
   198  				}
   199  			}
   200  			serve(ctx, l, server.ServeDebug)
   201  		}
   202  		if config.Metrics.Address != "" {
   203  			l, err := net.Listen("tcp", config.Metrics.Address)
   204  			if err != nil {
   205  				return errors.Wrapf(err, "failed to get listener for metrics endpoint")
   206  			}
   207  			serve(ctx, l, server.ServeMetrics)
   208  		}
   209  		// setup the ttrpc endpoint
   210  		tl, err := sys.GetLocalListener(config.TTRPC.Address, config.TTRPC.UID, config.TTRPC.GID)
   211  		if err != nil {
   212  			return errors.Wrapf(err, "failed to get listener for main ttrpc endpoint")
   213  		}
   214  		serve(ctx, tl, server.ServeTTRPC)
   215  
   216  		if config.GRPC.TCPAddress != "" {
   217  			l, err := net.Listen("tcp", config.GRPC.TCPAddress)
   218  			if err != nil {
   219  				return errors.Wrapf(err, "failed to get listener for TCP grpc endpoint")
   220  			}
   221  			serve(ctx, l, server.ServeTCP)
   222  		}
   223  		// setup the main grpc endpoint
   224  		l, err := sys.GetLocalListener(config.GRPC.Address, config.GRPC.UID, config.GRPC.GID)
   225  		if err != nil {
   226  			return errors.Wrapf(err, "failed to get listener for main endpoint")
   227  		}
   228  		serve(ctx, l, server.ServeGRPC)
   229  
   230  		if err := notifyReady(ctx); err != nil {
   231  			log.G(ctx).WithError(err).Warn("notify ready failed")
   232  		}
   233  
   234  		log.G(ctx).Infof("containerd successfully booted in %fs", time.Since(start).Seconds())
   235  		<-done
   236  		return nil
   237  	}
   238  	return app
   239  }
   240  
   241  func serve(ctx gocontext.Context, l net.Listener, serveFunc func(net.Listener) error) {
   242  	path := l.Addr().String()
   243  	log.G(ctx).WithField("address", path).Info("serving...")
   244  	go func() {
   245  		defer l.Close()
   246  		if err := serveFunc(l); err != nil {
   247  			log.G(ctx).WithError(err).WithField("address", path).Fatal("serve failure")
   248  		}
   249  	}()
   250  }
   251  
   252  func applyFlags(context *cli.Context, config *srvconfig.Config) error {
   253  	// the order for config vs flag values is that flags will always override
   254  	// the config values if they are set
   255  	if err := setLevel(context, config); err != nil {
   256  		return err
   257  	}
   258  	for _, v := range []struct {
   259  		name string
   260  		d    *string
   261  	}{
   262  		{
   263  			name: "root",
   264  			d:    &config.Root,
   265  		},
   266  		{
   267  			name: "state",
   268  			d:    &config.State,
   269  		},
   270  		{
   271  			name: "address",
   272  			d:    &config.GRPC.Address,
   273  		},
   274  	} {
   275  		if s := context.GlobalString(v.name); s != "" {
   276  			*v.d = s
   277  		}
   278  	}
   279  
   280  	applyPlatformFlags(context)
   281  
   282  	return nil
   283  }
   284  
   285  func setLevel(context *cli.Context, config *srvconfig.Config) error {
   286  	l := context.GlobalString("log-level")
   287  	if l == "" {
   288  		l = config.Debug.Level
   289  	}
   290  	if l != "" {
   291  		lvl, err := log.ParseLevel(l)
   292  		if err != nil {
   293  			return err
   294  		}
   295  		logrus.SetLevel(lvl)
   296  	}
   297  	return nil
   298  }
   299  
   300  func dumpStacks(writeToFile bool) {
   301  	var (
   302  		buf       []byte
   303  		stackSize int
   304  	)
   305  	bufferLen := 16384
   306  	for stackSize == len(buf) {
   307  		buf = make([]byte, bufferLen)
   308  		stackSize = runtime.Stack(buf, true)
   309  		bufferLen *= 2
   310  	}
   311  	buf = buf[:stackSize]
   312  	logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
   313  
   314  	if writeToFile {
   315  		// Also write to file to aid gathering diagnostics
   316  		name := filepath.Join(os.TempDir(), fmt.Sprintf("containerd.%d.stacks.log", os.Getpid()))
   317  		f, err := os.Create(name)
   318  		if err != nil {
   319  			return
   320  		}
   321  		defer f.Close()
   322  		f.WriteString(string(buf))
   323  		logrus.Infof("goroutine stack dump written to %s", name)
   324  	}
   325  }