github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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 }