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