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 }