github.com/lalkh/containerd@v1.4.3/services/server/server.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 server
    18  
    19  import (
    20  	"context"
    21  	"expvar"
    22  	"io"
    23  	"net"
    24  	"net/http"
    25  	"net/http/pprof"
    26  	"os"
    27  	"path/filepath"
    28  	"strings"
    29  	"sync"
    30  	"time"
    31  
    32  	csapi "github.com/containerd/containerd/api/services/content/v1"
    33  	ssapi "github.com/containerd/containerd/api/services/snapshots/v1"
    34  	"github.com/containerd/containerd/content"
    35  	"github.com/containerd/containerd/content/local"
    36  	csproxy "github.com/containerd/containerd/content/proxy"
    37  	"github.com/containerd/containerd/defaults"
    38  	"github.com/containerd/containerd/diff"
    39  	"github.com/containerd/containerd/events/exchange"
    40  	"github.com/containerd/containerd/log"
    41  	"github.com/containerd/containerd/metadata"
    42  	"github.com/containerd/containerd/pkg/dialer"
    43  	"github.com/containerd/containerd/pkg/timeout"
    44  	"github.com/containerd/containerd/plugin"
    45  	srvconfig "github.com/containerd/containerd/services/server/config"
    46  	"github.com/containerd/containerd/snapshots"
    47  	ssproxy "github.com/containerd/containerd/snapshots/proxy"
    48  	"github.com/containerd/containerd/sys"
    49  	"github.com/containerd/ttrpc"
    50  	metrics "github.com/docker/go-metrics"
    51  	grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
    52  	"github.com/pkg/errors"
    53  	bolt "go.etcd.io/bbolt"
    54  	"google.golang.org/grpc"
    55  	"google.golang.org/grpc/backoff"
    56  	"google.golang.org/grpc/credentials"
    57  )
    58  
    59  // CreateTopLevelDirectories creates the top-level root and state directories.
    60  func CreateTopLevelDirectories(config *srvconfig.Config) error {
    61  	switch {
    62  	case config.Root == "":
    63  		return errors.New("root must be specified")
    64  	case config.State == "":
    65  		return errors.New("state must be specified")
    66  	case config.Root == config.State:
    67  		return errors.New("root and state must be different paths")
    68  	}
    69  
    70  	if err := sys.MkdirAllWithACL(config.Root, 0711); err != nil {
    71  		return err
    72  	}
    73  
    74  	return sys.MkdirAllWithACL(config.State, 0711)
    75  }
    76  
    77  // New creates and initializes a new containerd server
    78  func New(ctx context.Context, config *srvconfig.Config) (*Server, error) {
    79  	if err := apply(ctx, config); err != nil {
    80  		return nil, err
    81  	}
    82  	for key, sec := range config.Timeouts {
    83  		d, err := time.ParseDuration(sec)
    84  		if err != nil {
    85  			return nil, errors.Errorf("unable to parse %s into a time duration", sec)
    86  		}
    87  		timeout.Set(key, d)
    88  	}
    89  	plugins, err := LoadPlugins(ctx, config)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	for id, p := range config.StreamProcessors {
    94  		diff.RegisterProcessor(diff.BinaryHandler(id, p.Returns, p.Accepts, p.Path, p.Args))
    95  	}
    96  
    97  	serverOpts := []grpc.ServerOption{
    98  		grpc.UnaryInterceptor(grpc_prometheus.UnaryServerInterceptor),
    99  		grpc.StreamInterceptor(grpc_prometheus.StreamServerInterceptor),
   100  	}
   101  	if config.GRPC.MaxRecvMsgSize > 0 {
   102  		serverOpts = append(serverOpts, grpc.MaxRecvMsgSize(config.GRPC.MaxRecvMsgSize))
   103  	}
   104  	if config.GRPC.MaxSendMsgSize > 0 {
   105  		serverOpts = append(serverOpts, grpc.MaxSendMsgSize(config.GRPC.MaxSendMsgSize))
   106  	}
   107  	ttrpcServer, err := newTTRPCServer()
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	tcpServerOpts := serverOpts
   112  	if config.GRPC.TCPTLSCert != "" {
   113  		log.G(ctx).Info("setting up tls on tcp GRPC services...")
   114  		creds, err := credentials.NewServerTLSFromFile(config.GRPC.TCPTLSCert, config.GRPC.TCPTLSKey)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  		tcpServerOpts = append(tcpServerOpts, grpc.Creds(creds))
   119  	}
   120  	var (
   121  		grpcServer = grpc.NewServer(serverOpts...)
   122  		tcpServer  = grpc.NewServer(tcpServerOpts...)
   123  
   124  		grpcServices  []plugin.Service
   125  		tcpServices   []plugin.TCPService
   126  		ttrpcServices []plugin.TTRPCService
   127  
   128  		s = &Server{
   129  			grpcServer:  grpcServer,
   130  			tcpServer:   tcpServer,
   131  			ttrpcServer: ttrpcServer,
   132  			events:      exchange.NewExchange(),
   133  			config:      config,
   134  		}
   135  		initialized = plugin.NewPluginSet()
   136  		required    = make(map[string]struct{})
   137  	)
   138  	for _, r := range config.RequiredPlugins {
   139  		required[r] = struct{}{}
   140  	}
   141  	for _, p := range plugins {
   142  		id := p.URI()
   143  		reqID := id
   144  		if config.GetVersion() == 1 {
   145  			reqID = p.ID
   146  		}
   147  		log.G(ctx).WithField("type", p.Type).Infof("loading plugin %q...", id)
   148  
   149  		initContext := plugin.NewContext(
   150  			ctx,
   151  			p,
   152  			initialized,
   153  			config.Root,
   154  			config.State,
   155  		)
   156  		initContext.Events = s.events
   157  		initContext.Address = config.GRPC.Address
   158  		initContext.TTRPCAddress = config.TTRPC.Address
   159  
   160  		// load the plugin specific configuration if it is provided
   161  		if p.Config != nil {
   162  			pc, err := config.Decode(p)
   163  			if err != nil {
   164  				return nil, err
   165  			}
   166  			initContext.Config = pc
   167  		}
   168  		result := p.Init(initContext)
   169  		if err := initialized.Add(result); err != nil {
   170  			return nil, errors.Wrapf(err, "could not add plugin result to plugin set")
   171  		}
   172  
   173  		instance, err := result.Instance()
   174  		if err != nil {
   175  			if plugin.IsSkipPlugin(err) {
   176  				log.G(ctx).WithError(err).WithField("type", p.Type).Infof("skip loading plugin %q...", id)
   177  			} else {
   178  				log.G(ctx).WithError(err).Warnf("failed to load plugin %s", id)
   179  			}
   180  			if _, ok := required[reqID]; ok {
   181  				return nil, errors.Wrapf(err, "load required plugin %s", id)
   182  			}
   183  			continue
   184  		}
   185  
   186  		delete(required, reqID)
   187  		// check for grpc services that should be registered with the server
   188  		if src, ok := instance.(plugin.Service); ok {
   189  			grpcServices = append(grpcServices, src)
   190  		}
   191  		if src, ok := instance.(plugin.TTRPCService); ok {
   192  			ttrpcServices = append(ttrpcServices, src)
   193  		}
   194  		if service, ok := instance.(plugin.TCPService); ok {
   195  			tcpServices = append(tcpServices, service)
   196  		}
   197  
   198  		s.plugins = append(s.plugins, result)
   199  	}
   200  	if len(required) != 0 {
   201  		var missing []string
   202  		for id := range required {
   203  			missing = append(missing, id)
   204  		}
   205  		return nil, errors.Errorf("required plugin %s not included", missing)
   206  	}
   207  
   208  	// register services after all plugins have been initialized
   209  	for _, service := range grpcServices {
   210  		if err := service.Register(grpcServer); err != nil {
   211  			return nil, err
   212  		}
   213  	}
   214  	for _, service := range ttrpcServices {
   215  		if err := service.RegisterTTRPC(ttrpcServer); err != nil {
   216  			return nil, err
   217  		}
   218  	}
   219  	for _, service := range tcpServices {
   220  		if err := service.RegisterTCP(tcpServer); err != nil {
   221  			return nil, err
   222  		}
   223  	}
   224  	return s, nil
   225  }
   226  
   227  // Server is the containerd main daemon
   228  type Server struct {
   229  	grpcServer  *grpc.Server
   230  	ttrpcServer *ttrpc.Server
   231  	tcpServer   *grpc.Server
   232  	events      *exchange.Exchange
   233  	config      *srvconfig.Config
   234  	plugins     []*plugin.Plugin
   235  }
   236  
   237  // ServeGRPC provides the containerd grpc APIs on the provided listener
   238  func (s *Server) ServeGRPC(l net.Listener) error {
   239  	if s.config.Metrics.GRPCHistogram {
   240  		// enable grpc time histograms to measure rpc latencies
   241  		grpc_prometheus.EnableHandlingTimeHistogram()
   242  	}
   243  	// before we start serving the grpc API register the grpc_prometheus metrics
   244  	// handler.  This needs to be the last service registered so that it can collect
   245  	// metrics for every other service
   246  	grpc_prometheus.Register(s.grpcServer)
   247  	return trapClosedConnErr(s.grpcServer.Serve(l))
   248  }
   249  
   250  // ServeTTRPC provides the containerd ttrpc APIs on the provided listener
   251  func (s *Server) ServeTTRPC(l net.Listener) error {
   252  	return trapClosedConnErr(s.ttrpcServer.Serve(context.Background(), l))
   253  }
   254  
   255  // ServeMetrics provides a prometheus endpoint for exposing metrics
   256  func (s *Server) ServeMetrics(l net.Listener) error {
   257  	m := http.NewServeMux()
   258  	m.Handle("/v1/metrics", metrics.Handler())
   259  	return trapClosedConnErr(http.Serve(l, m))
   260  }
   261  
   262  // ServeTCP allows services to serve over tcp
   263  func (s *Server) ServeTCP(l net.Listener) error {
   264  	grpc_prometheus.Register(s.tcpServer)
   265  	return trapClosedConnErr(s.tcpServer.Serve(l))
   266  }
   267  
   268  // ServeDebug provides a debug endpoint
   269  func (s *Server) ServeDebug(l net.Listener) error {
   270  	// don't use the default http server mux to make sure nothing gets registered
   271  	// that we don't want to expose via containerd
   272  	m := http.NewServeMux()
   273  	m.Handle("/debug/vars", expvar.Handler())
   274  	m.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
   275  	m.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
   276  	m.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
   277  	m.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
   278  	m.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
   279  	return trapClosedConnErr(http.Serve(l, m))
   280  }
   281  
   282  // Stop the containerd server canceling any open connections
   283  func (s *Server) Stop() {
   284  	s.grpcServer.Stop()
   285  	for i := len(s.plugins) - 1; i >= 0; i-- {
   286  		p := s.plugins[i]
   287  		instance, err := p.Instance()
   288  		if err != nil {
   289  			log.L.WithError(err).WithField("id", p.Registration.URI()).
   290  				Errorf("could not get plugin instance")
   291  			continue
   292  		}
   293  		closer, ok := instance.(io.Closer)
   294  		if !ok {
   295  			continue
   296  		}
   297  		if err := closer.Close(); err != nil {
   298  			log.L.WithError(err).WithField("id", p.Registration.URI()).
   299  				Errorf("failed to close plugin")
   300  		}
   301  	}
   302  }
   303  
   304  // LoadPlugins loads all plugins into containerd and generates an ordered graph
   305  // of all plugins.
   306  func LoadPlugins(ctx context.Context, config *srvconfig.Config) ([]*plugin.Registration, error) {
   307  	// load all plugins into containerd
   308  	path := config.PluginDir
   309  	if path == "" {
   310  		path = filepath.Join(config.Root, "plugins")
   311  	}
   312  	if err := plugin.Load(path); err != nil {
   313  		return nil, err
   314  	}
   315  	// load additional plugins that don't automatically register themselves
   316  	plugin.Register(&plugin.Registration{
   317  		Type: plugin.ContentPlugin,
   318  		ID:   "content",
   319  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
   320  			ic.Meta.Exports["root"] = ic.Root
   321  			return local.NewStore(ic.Root)
   322  		},
   323  	})
   324  	plugin.Register(&plugin.Registration{
   325  		Type: plugin.MetadataPlugin,
   326  		ID:   "bolt",
   327  		Requires: []plugin.Type{
   328  			plugin.ContentPlugin,
   329  			plugin.SnapshotPlugin,
   330  		},
   331  		Config: &srvconfig.BoltConfig{
   332  			ContentSharingPolicy: srvconfig.SharingPolicyShared,
   333  		},
   334  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
   335  			if err := os.MkdirAll(ic.Root, 0711); err != nil {
   336  				return nil, err
   337  			}
   338  			cs, err := ic.Get(plugin.ContentPlugin)
   339  			if err != nil {
   340  				return nil, err
   341  			}
   342  
   343  			snapshottersRaw, err := ic.GetByType(plugin.SnapshotPlugin)
   344  			if err != nil {
   345  				return nil, err
   346  			}
   347  
   348  			snapshotters := make(map[string]snapshots.Snapshotter)
   349  			for name, sn := range snapshottersRaw {
   350  				sn, err := sn.Instance()
   351  				if err != nil {
   352  					if !plugin.IsSkipPlugin(err) {
   353  						log.G(ic.Context).WithError(err).
   354  							Warnf("could not use snapshotter %v in metadata plugin", name)
   355  					}
   356  					continue
   357  				}
   358  				snapshotters[name] = sn.(snapshots.Snapshotter)
   359  			}
   360  
   361  			shared := true
   362  			ic.Meta.Exports["policy"] = srvconfig.SharingPolicyShared
   363  			if cfg, ok := ic.Config.(*srvconfig.BoltConfig); ok {
   364  				if cfg.ContentSharingPolicy != "" {
   365  					if err := cfg.Validate(); err != nil {
   366  						return nil, err
   367  					}
   368  					if cfg.ContentSharingPolicy == srvconfig.SharingPolicyIsolated {
   369  						ic.Meta.Exports["policy"] = srvconfig.SharingPolicyIsolated
   370  						shared = false
   371  					}
   372  
   373  					log.L.WithField("policy", cfg.ContentSharingPolicy).Info("metadata content store policy set")
   374  				}
   375  			}
   376  
   377  			path := filepath.Join(ic.Root, "meta.db")
   378  			ic.Meta.Exports["path"] = path
   379  
   380  			db, err := bolt.Open(path, 0644, nil)
   381  			if err != nil {
   382  				return nil, err
   383  			}
   384  
   385  			var dbopts []metadata.DBOpt
   386  			if !shared {
   387  				dbopts = append(dbopts, metadata.WithPolicyIsolated)
   388  			}
   389  			mdb := metadata.NewDB(db, cs.(content.Store), snapshotters, dbopts...)
   390  			if err := mdb.Init(ic.Context); err != nil {
   391  				return nil, err
   392  			}
   393  			return mdb, nil
   394  		},
   395  	})
   396  
   397  	clients := &proxyClients{}
   398  	for name, pp := range config.ProxyPlugins {
   399  		var (
   400  			t plugin.Type
   401  			f func(*grpc.ClientConn) interface{}
   402  
   403  			address = pp.Address
   404  		)
   405  
   406  		switch pp.Type {
   407  		case string(plugin.SnapshotPlugin), "snapshot":
   408  			t = plugin.SnapshotPlugin
   409  			ssname := name
   410  			f = func(conn *grpc.ClientConn) interface{} {
   411  				return ssproxy.NewSnapshotter(ssapi.NewSnapshotsClient(conn), ssname)
   412  			}
   413  
   414  		case string(plugin.ContentPlugin), "content":
   415  			t = plugin.ContentPlugin
   416  			f = func(conn *grpc.ClientConn) interface{} {
   417  				return csproxy.NewContentStore(csapi.NewContentClient(conn))
   418  			}
   419  		default:
   420  			log.G(ctx).WithField("type", pp.Type).Warn("unknown proxy plugin type")
   421  		}
   422  
   423  		plugin.Register(&plugin.Registration{
   424  			Type: t,
   425  			ID:   name,
   426  			InitFn: func(ic *plugin.InitContext) (interface{}, error) {
   427  				ic.Meta.Exports["address"] = address
   428  				conn, err := clients.getClient(address)
   429  				if err != nil {
   430  					return nil, err
   431  				}
   432  				return f(conn), nil
   433  			},
   434  		})
   435  
   436  	}
   437  
   438  	filter := srvconfig.V2DisabledFilter
   439  	if config.GetVersion() == 1 {
   440  		filter = srvconfig.V1DisabledFilter
   441  	}
   442  	// return the ordered graph for plugins
   443  	return plugin.Graph(filter(config.DisabledPlugins)), nil
   444  }
   445  
   446  type proxyClients struct {
   447  	m       sync.Mutex
   448  	clients map[string]*grpc.ClientConn
   449  }
   450  
   451  func (pc *proxyClients) getClient(address string) (*grpc.ClientConn, error) {
   452  	pc.m.Lock()
   453  	defer pc.m.Unlock()
   454  	if pc.clients == nil {
   455  		pc.clients = map[string]*grpc.ClientConn{}
   456  	} else if c, ok := pc.clients[address]; ok {
   457  		return c, nil
   458  	}
   459  
   460  	backoffConfig := backoff.DefaultConfig
   461  	backoffConfig.MaxDelay = 3 * time.Second
   462  	connParams := grpc.ConnectParams{
   463  		Backoff: backoffConfig,
   464  	}
   465  	gopts := []grpc.DialOption{
   466  		grpc.WithInsecure(),
   467  		grpc.WithConnectParams(connParams),
   468  		grpc.WithContextDialer(dialer.ContextDialer),
   469  
   470  		// TODO(stevvooe): We may need to allow configuration of this on the client.
   471  		grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
   472  		grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
   473  	}
   474  
   475  	conn, err := grpc.Dial(dialer.DialAddress(address), gopts...)
   476  	if err != nil {
   477  		return nil, errors.Wrapf(err, "failed to dial %q", address)
   478  	}
   479  
   480  	pc.clients[address] = conn
   481  
   482  	return conn, nil
   483  }
   484  
   485  func trapClosedConnErr(err error) error {
   486  	if err == nil {
   487  		return nil
   488  	}
   489  	if strings.Contains(err.Error(), "use of closed network connection") {
   490  		return nil
   491  	}
   492  	return err
   493  }