func newDaemonCommand() *cobra.Command { opts := daemonOptions{ daemonConfig: daemon.NewConfig(), common: cliflags.NewCommonOptions(), } cmd := &cobra.Command{ Use: "dockerd [OPTIONS]", Short: "A self-sufficient runtime for containers.", SilenceUsage: true, SilenceErrors: true, Args: cli.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { opts.flags = cmd.Flags() return runDaemon(opts) }, } cli.SetupRootCommand(cmd) flags := cmd.Flags() flags.BoolVarP(&opts.version, "version", "v", false, "Print version information and quit") flags.StringVar(&opts.configFile, flagDaemonConfigFile, defaultDaemonConfigFile, "Daemon configuration file") opts.common.InstallFlags(flags) opts.daemonConfig.InstallFlags(flags) installServiceFlags(flags) return cmd }
该方法中像docker client一样解析命令,dockerd启动deamon实际运行的是runDeamon(),可以使用docker -D启动,会将debug打印的内容也显示出来,进入renDeamon()后,初始化daemonCli,执行start方法,runDeamon中除了api service还会d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote),注册registry,和contanerd等(还没有看)。
api := apiserver.New(serverConfig) cli.api = api for i := 0; i < len(cli.Config.Hosts); i++ { var err error if cli.Config.Hosts[i], err = dopts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { return fmt.Errorf("error parsing -H %s : %v", cli.Config.Hosts[i], err) } protoAddr := cli.Config.Hosts[i] protoAddrParts := strings.SplitN(protoAddr, "://", 2) if len(protoAddrParts) != 2 { return fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr) } proto := protoAddrParts[0] addr := protoAddrParts[1] // It's a bad idea to bind to TCP without tlsverify. if proto == "tcp" && (serverConfig.TLSConfig == nil || serverConfig.TLSConfig.ClientAuth != tls.RequireAndVerifyClientCert) { logrus.Warn("[!] DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING [!]") } ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig) if err != nil { return err } ls = wrapListeners(proto, ls) // If we're binding to a TCP port, make sure that a container doesn't try to use it. if proto == "tcp" { if err := allocateDaemonPort(addr); err != nil { return err } } logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr) api.Accept(addr, ls...) }
这里会遍历docker -H中指定的地址,也就是deamon启动时我们可以配置TCP,UNIX socket来启动服务。
func Init(proto, addr, socketGroup string, tlsConfig *tls.Config) (ls []net.Listener, err error) { switch proto { case "tcp": l, err := sockets.NewTCPSocket(addr, tlsConfig) if err != nil { return nil, err } ls = append(ls, l) case "unix": l, err := sockets.NewUnixSocket(addr, socketGroup) if err != nil { return nil, fmt.Errorf("can't create unix socket %s: %v", addr, err) } ls = append(ls, l) default: return nil, fmt.Errorf("Invalid protocol format: %q", proto) } return }
然后这个时候有ls []net.Listener和配置的addr,然后将它们初始化到了一个HTTPServer中
type HTTPServer struct { srv *http.Server l net.Listener }
func (s *Server) Accept(addr string, listeners ...net.Listener) { for _, listener := range listeners { httpServer := &HTTPServer{ srv: &http.Server{ Addr: addr, }, l: listener, } s.servers = append(s.servers, httpServer) } }
然后添加路由,initRouter(api, d, c),这是对route进行初始化,将method,path,hangdlefunc封装到了route{}中,然后使用了第3方的路由gorilla/mux(支持正则路由,.path().methods()也挺好玩的)来进行添加。makeHTTPHandler()方法将
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error转换为http.handlefunc,
func (s *Server) createMux() *mux.Router { m := mux.NewRouter() logrus.Debug("Registering routers") for _, apiRouter := range s.routers { for _, r := range apiRouter.Routes() { f := s.makeHTTPHandler(r.Handler()) logrus.Debugf("Registering %s, %s", r.Method(), r.Path()) m.Path(versionMatcher + r.Path()).Methods(r.Method()).Handler(f) m.Path(r.Path()).Methods(r.Method()).Handler(f) } } err := errors.NewRequestNotFoundError(fmt.Errorf("page not found")) notFoundHandler := httputils.MakeErrorHandler(err) m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler) m.NotFoundHandler = notFoundHandler return m }
然后监听端口,监听端口的本质就是*http.Server.serve(l),这里将http.Server和net.Listener封装到了一起。
type HTTPServer struct { srv *http.Server l net.Listener }
值得一提的是go为每个服务都开启了一个协程,然后使用err = srv.Serve()执行的err来阻塞协程。
func (s *Server) serveAPI() error { var chErrors = make(chan error, len(s.servers)) for _, srv := range s.servers { srv.srv.Handler = s.routerSwapper go func(srv *HTTPServer) { var err error logrus.Infof("API listen on %s", srv.l.Addr()) if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { err = nil } chErrors <- err }(srv) } for i := 0; i < len(s.servers); i++ { err := <-chErrors if err != nil { return err } } return nil }
serveAPIWait := make(chan error) go api.Wait(serveAPIWait) // after the daemon is done setting up we can notify systemd api notifySystem() // Daemon is fully initialized and handling API traffic // Wait for serve API to complete errAPI := <-serveAPIWait