Docker基础之Client端与Daemon端的通信

一、Docker的Client端

1、 DockerCli结构体封装
前面说到会生成一个DockerCli struct结构体对象,解析文件在在这个路径下:/cli/command/cli.go
docker Client解析了命令行之后,会通过其中的client.APIClient和docker Server端进行通信,其结构体定义函数和实现类函数的定义如下所示:

type DockerCli struct {
	configFile *configfile.ConfigFile //配置文件
	in         *InStream //输入流
	out        *OutStream //输出流
	err        io.Writer // 出错信息
	keyFile    string //关键文件路径
	client     client.APIClient //客户端
	hasExperimental bool  
	defaultVersion  string //默认版本
}
func (cli *DockerCli) Client() client.APIClient {
	return cli.client
}

2、docker 镜像
我们以docker image ls为例子,研究docker的Client端是怎么把信息发给Server端的。 下面是截取的部分源码:

func runImages(dockerCli *command.DockerCli, opts imagesOptions) error {
	ctx := context.Background()
	filters := opts.filter.Value()
	if opts.matchName != "" {
		filters.Add("reference", opts.matchName)
	}

	options := types.ImageListOptions{
		All:     opts.all,
		Filters: filters,
	}

	/*
		访问docker的server端,获取images列表
	*/
	images, err := dockerCli.Client().ImageList(ctx, options)
	if err != nil {
		return err
	}
	...
	...
	...
}

dockerCli.Client()获取到的正是前面所说的client.APIClient,我们来看看ImageList(),下面是部分源码:

func (cli *Client) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
	...
	...
	...

	serverResp, err := cli.get(ctx, "/images/json", query, nil)
	if err != nil {
		return images, err
	}

	err = json.NewDecoder(serverResp.body).Decode(&images)
	ensureReaderClosed(serverResp)
	return images, err
}

3、APIClient 接口 和 Client 结构体对象
关于dockerCli中client.APIClient的初始化,官方的描述是creates a new APIClient from command line flags,下面是截取的部分源码:

//APIClient接口
type APIClient interface {
	CommonAPIClient
	apiClientExperimental
}
var _ APIClient = &Client{}

4、Client 结构体

type Client struct {
	// 定义客户端格式
	scheme string
	// 服务端连接的主机地址
	host string
	// 定义哦客户端的协议
	proto string
	// 存储客户端地址
	addr string
	// 用于存储保存初始请求路径
	basePath string
	// 定义客户端的传输的和接收的协议为HTTP
	client *http.Client
	// 服务端传输的版本信息
	version string
	// 自定义http报文头部信息,进而实现加密传输
	customHTTPHeaders map[string]string
	// 当用户设置版本时,manualOverride 设置为 true。
	manualOverride bool
}
func NewClient(host string, version string, client *http.Client, httpHeaders map[string]string) (*Client, error) {
	proto, addr, basePath, err := ParseHost(host)
	if err != nil {
		return nil, err
	}

	if client != nil {
		if _, ok := client.Transport.(*http.Transport); !ok {
			return nil, fmt.Errorf("unable to verify TLS configuration, invalid transport %v", client.Transport)
		}
	} else {
		transport := new(http.Transport)
		sockets.ConfigureTransport(transport, proto, addr)
		client = &http.Client{
			Transport: transport,
		}
	}

	scheme := "http"
	tlsConfig := resolveTLSConfig(client.Transport)
	if tlsConfig != nil {
		scheme = "https"
	}

	return &Client{
		scheme:            scheme,
		host:              host,
		proto:             proto,
		addr:              addr,
		basePath:          basePath,
		client:            client,
		version:           version,
		customHTTPHeaders: httpHeaders,
	}, nil
}

接着上面docker image ls,查看Client提供的get(),下面是部分源码:

// getWithContext 使用方法 GET 和特定的 go 上下文向 docker API 发送 http 请求。
func (cli *Client) get(ctx context.Context, path string, query url.Values, headers map[string][]string) (serverResponse, error) {
	return cli.sendRequest(ctx, "GET", path, query, nil, headers)
}

func (cli *Client) sendRequest(ctx context.Context, method, path string, query url.Values, body io.Reader, headers headers) (serverResponse, error) {
	req, err := cli.buildRequest(method, cli.getAPIPath(path, query), body, headers)
	if err != nil {
		return serverResponse{}, err
	}
	//客户端发送请求
	return cli.doRequest(ctx, req)
}

func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResponse, error) {
	serverResp := serverResponse{statusCode: -1}
	//发送请求,接收响应信息response
	resp, err := ctxhttp.Do(ctx, cli.client, req)
	if err != nil {
		...
		...
	}
	//开始处理response信息
	if resp != nil {
		serverResp.statusCode = resp.StatusCode
	}

	if serverResp.statusCode < 200 || serverResp.statusCode >= 400 {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return serverResp, err
		}
		if len(body) == 0 {
			return serverResp, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(serverResp.statusCode), req.URL)
		}

		var errorMessage string
		if (cli.version == "" || versions.GreaterThan(cli.version, "1.23")) &&
			resp.Header.Get("Content-Type") == "application/json" {
			var errorResponse types.ErrorResponse
			if err := json.Unmarshal(body, &errorResponse); err != nil {
				return serverResp, fmt.Errorf("Error reading JSON: %v", err)
			}
			errorMessage = errorResponse.Message
		} else {
			errorMessage = string(body)
		}

		return serverResp, fmt.Errorf("Error response from daemon: %s", strings.TrimSpace(errorMessage))
	}

	serverResp.body = resp.Body
	serverResp.header = resp.Header
	return serverResp, nil
}

至此,docker client端的信息已经发送给docker daemon端了,并取得Response信息。 其它所有的命令都是类似的

二、docker daemon端

首先来看看docker daemon端的启动流程。
1、实例化一个type DaemonCli struct对象
2、 启动daemonCli

func runDaemon(opts daemonOptions) error {
	if opts.version {
		showVersion()
		return nil
	}
	/*
		创建daemon客户端对象
	*/
	daemonCli := NewDaemonCli()
	if stop {
		return nil
	}
	/*
		启动daemonCli
	*/
	err = daemonCli.start(opts)
	notifyShutdown(err)
	return err
}

1、DaemonCli 结构体对象
DaemonCli 结构体包含了配置信息,配置文件,参数信息,APIServer,Daemon对象,authzMiddleware认证插件

type DaemonCli struct {
  	*daemon.Config
	configFile *string
	flags      *pflag.FlagSet
	api *apiserver.Server
	d               *daemon.Daemon
	authzMiddleware *authorization.Middleware 
}
func NewDaemonCli() *DaemonCli {
	return &DaemonCli{}
}

2、Server结构体

type Server struct {
	cfg           *Config
	servers       []*HTTPServer
	routers       []router.Router
	routerSwapper *routerSwapper
	middlewares   []middleware.Middleware
}

1.HTTPServer

type HTTPServer struct {
	srv *http.Server
	l   net.Listener
}

2.routerSwapper

type routerSwapper struct {
	mu     sync.Mutex
	router *mux.Router
}

3、 启动daemonCli
接着上面的主体,启动daemonCli,daemonCli.start(opts),其主要流程如下:

  1. 设置相关参数
  2. 创建一个type Server struct对象,同时设置监控地址
  3. registryService,daemon程序在pull镜像等操作时,需要与registry服务交互
  4. 创建与docker-containerd通信的对象containerdRemote
  5. 创建Daemon对象,daemon.NewDaemon(cli.Config, registryService, containerdRemote)
  6. 初始化API Server的路由
  7. 起一个groutine来启动server
func (cli *DaemonCli) start(opts daemonOptions) (err error) {
	stopc := make(chan bool)
	defer close(stopc)

	// 运行守护程序时从 uuid 包发出警告
	uuid.Loggerf = logrus.Warnf

	/*
		==>/cli/flags/common.go ==>func (commonOpts *CommonOptions) SetDefaultOptions
		现在只有和TLS相关的参数
	*/
	opts.common.SetDefaultOptions(opts.flags)

	if cli.Config, err = loadDaemonCliConfig(opts); err != nil {
		return err
	}
	cli.configFile = &opts.configFile
	cli.flags = opts.flags

	if opts.common.TrustKey == "" {
		opts.common.TrustKey = filepath.Join(
			getDaemonConfDir(cli.Config.Root),
			cliflags.DefaultTrustKeyFile)
	}

	if cli.Config.Debug {
		utils.EnableDebug()
	}

	if cli.Config.Experimental {
		logrus.Warn("Running experimental build")
	}

	logrus.SetFormatter(&logrus.TextFormatter{
		TimestampFormat: jsonlog.RFC3339NanoFixed,
		DisableColors:   cli.Config.RawLogs,
	})

	if err := setDefaultUmask(); err != nil {
		return fmt.Errorf("Failed to set umask: %v", err)
	}

	if len(cli.LogConfig.Config) > 0 {
		if err := logger.ValidateLogOpts(cli.LogConfig.Type, cli.LogConfig.Config); err != nil {
			return fmt.Errorf("Failed to set log opts: %v", err)
		}
	}

	// 在创建任何其他文件(PID 或迁移密钥)之前创建守护程序根目录
	//以确保设置了适当的 ACL(在 Windows 上尤其相关)
	if err := daemon.CreateDaemonRoot(cli.Config); err != nil {
		return err
	}

	if cli.Pidfile != "" {
		pf, err := pidfile.New(cli.Pidfile)
		if err != nil {
			return fmt.Errorf("Error starting daemon: %v", err)
		}
		defer func() {
			if err := pf.Remove(); err != nil {
				logrus.Error(err)
			}
		}()
	}

	serverConfig := &apiserver.Config{
		Logging:     true,
		SocketGroup: cli.Config.SocketGroup,
		Version:     dockerversion.Version,
		EnableCors:  cli.Config.EnableCors,
		CorsHeaders: cli.Config.CorsHeaders,
	}

	if cli.Config.TLS {
		tlsOptions := tlsconfig.Options{
			CAFile:   cli.Config.CommonTLSOptions.CAFile,
			CertFile: cli.Config.CommonTLSOptions.CertFile,
			KeyFile:  cli.Config.CommonTLSOptions.KeyFile,
		}

		if cli.Config.TLSVerify {
			// 服务器要求并验证客户端的证书
			tlsOptions.ClientAuth = tls.RequireAndVerifyClientCert
		}
		tlsConfig, err := tlsconfig.Server(tlsOptions)
		if err != nil {
			return err
		}
		serverConfig.TLSConfig = tlsConfig
	}

	if len(cli.Config.Hosts) == 0 {
		cli.Config.Hosts = make([]string, 1)
	}

	//创建一个type Server struct对象
	api := apiserver.New(serverConfig)
	cli.api = api
	/*
	   daemon程序可以设置监控多个地址
	   默认情况下,cli.Config.Hosts 长度为 1,监控地址为unix:///var/run/docker.sock
	*/
	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的值: unix:///var/run/docker.sock
		*/
		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]

		// 在没有tlsverify的情况下绑定到TCP是一个坏点。
		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 [!]")
		}
		/*
			根据host解析内容初始化监听器listener
				==>/pkg/listeners/listeners_unix.go
		*/
		ls, err := listeners.Init(proto, addr, serverConfig.SocketGroup, serverConfig.TLSConfig)
		if err != nil {
			return err
		}
		ls = wrapListeners(proto, ls)
		// 如果我们要绑定到 TCP 端口,请确保容器不会尝试使用它。
		if proto == "tcp" {
			if err := allocateDaemonPort(addr); err != nil {
				return err
			}
		}
		logrus.Debugf("Listener created for HTTP on %s (%s)", proto, addr)
		/*
			Accept()设置服务器接受连接的监听器
			addr is: /var/run/docker.sock
				==>/api/server/server.go
		*/
		api.Accept(addr, ls...)
	}

	if err := migrateKey(cli.Config); err != nil {
		return err
	}

	// FIXME: why is this down here instead of with the other TrustKey logic above?
	cli.TrustKeyPath = opts.common.TrustKey

	/*
		daemon程序在pull镜像等操作时,需要与registry服务交互,
		这里即创建了registryService对象,用于与registry服务交互
		a registry service应该实现的接口,见/registry/service.go
			==>type Service interface
	*/
	registryService := registry.NewService(cli.Config.ServiceOptions)
	/*
		创建与`docker-containerd`通信的对象containerdRemote
			==>/libcontainerd/remote_unix.go
				==>func New
		/libcontainerd目录下仅提供用于通信的接口函数,供daemon程序调用,以控制管理容器的运行
			==>/libcontainerd/types.go
				==>type Client interface
		通过`make all`进行编译的时候,会自行下载
	*/
	containerdRemote, err := libcontainerd.New(cli.getLibcontainerdRoot(), cli.getPlatformRemoteOptions()...)
	if err != nil {
		return err
	}
	/*
		声明监听系统的一些终止信号,
		如果监听到这些信息,就停止DaemonCli
	*/
	signal.Trap(func() {
		cli.stop()
		<-stopc // wait for daemonCli.start() to return
	})

	/*
		创建Daemon对象
			==>/daemon/daemon.go
				==>func NewDaemon
	*/
	d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
	if err != nil {
		return fmt.Errorf("Error starting daemon: %v", err)
	}

	if cli.Config.MetricsAddress != "" {
		if !d.HasExperimental() {
			return fmt.Errorf("metrics-addr is only supported when experimental is enabled")
		}
		if err := startMetricsServer(cli.Config.MetricsAddress); err != nil {
			return err
		}
	}

	name, _ := os.Hostname()

	//新建cluster对象
	c, err := cluster.New(cluster.Config{
		Root:                   cli.Config.Root,
		Name:                   name,
		Backend:                d,
		NetworkSubnetsProvider: d,
		DefaultAdvertiseAddr:   cli.Config.SwarmDefaultAdvertiseAddr,
		RuntimeRoot:            cli.getSwarmRunRoot(),
	})
	if err != nil {
		logrus.Fatalf("Error creating cluster component: %v", err)
	/*
		V1.12版本开始集成了swarm的相关功能,
		这里将自动启动安装有swarm endpoint的容器
	*/
	d.RestartSwarmContainers()

	logrus.Info("Daemon has completed initialization")

	logrus.WithFields(logrus.Fields{
		"version":     dockerversion.Version,
		"commit":      dockerversion.GitCommit,
		"graphdriver": d.GraphDriverName(),
	}).Info("Docker daemon")

	/*
		将新建的Daemon对象与DaemonCli相关联:cli.d = d
	*/
	cli.d = d
	/*
		给API Server注册一些中间件,
		主要进行版本兼容性检查、添加CORS跨站点请求相关响应头、对请求进行认证
	*/
	if err := cli.initMiddlewares(api, serverConfig); err != nil {
		logrus.Fatalf("Error creating middlewares: %v", err)
	}
	d.SetCluster(c)
	/*
		初始化API Server的路由
	*/
	initRouter(api, d, c)

	cli.setupConfigReloadTrap()
	/*
		起一个groutine来启动server
		如果出现error,channel serveAPIWait会收到信息
			==>/api/server/server.go
				==>func (s *Server) Wait(waitChan chan error)
	*/
	serveAPIWait := make(chan error)
	go api.Wait(serveAPIWait)

	// after the daemon is done setting up we can notify systemd api
	/*
		==>/cmd/dockerd/daemon_linux.go
			==>func notifySystem()
		给host主机发送一个信息,表示server已经Ready了,可以接收request了
	*/
	notifySystem()
	/*
		如果不出现error,channel serveAPIWait会阻塞
	*/
	errAPI := <-serveAPIWait
	c.Cleanup()                //有错误信息传出,对cluster进行清理操作
	shutdownDaemon(d)          //关闭Daemon
	containerdRemote.Cleanup() //关闭libcontainerd
	if errAPI != nil {
		return fmt.Errorf("Shutting down due to ServeAPI error: %v", errAPI)
	}

	return nil
}

4、路由注册
func initRouter 添加各种路由到routers中,然后根据路由表routers来初始化apiServer的路由器。

func initRouter(s *apiserver.Server, d *daemon.Daemon, c *cluster.Cluster) {
	/* 获取解码器decoder */
	decoder := runconfig.ContainerDecoder{}
	/*
		添加路由
	*/
	routers := []router.Router{
		/*
			添加的顺序有要求
		*/
		checkpointrouter.NewRouter(d, decoder),
		container.NewRouter(d, decoder),
		/*
			以image的路为例子
				==>/api/server/router/image/image.go
					==>	func NewRouter
		*/
		image.NewRouter(d, decoder),
		systemrouter.NewRouter(d, c),
		volume.NewRouter(d),
		build.NewRouter(dockerfile.NewBuildManager(d)),
		swarmrouter.NewRouter(c),
		pluginrouter.NewRouter(d.PluginManager()),
	}

	//网络相关路由
	if d.NetworkControllerEnabled() {
		routers = append(routers, network.NewRouter(d, c))
	}

	if d.HasExperimental() {
		/*
			Experimental模式下的api
		*/
		for _, r := range routers {
			for _, route := range r.Routes() {
				if experimental, ok := route.(router.ExperimentalRoute); ok {
					experimental.Enable()
				}
			}
		}
	}
	/*
		根据设置好的路由表routers来初始化apiServer的路由器
	*/
	s.InitRouter(utils.IsDebugEnabled(), routers...)
}

//初始化路由器初始化服务器的路由器列表。
//如果启用Profiler为真,则此方法还会启用Go探查器。
func (s *Server) InitRouter(enableProfiler bool, routers ...router.Router) {
	s.routers = append(s.routers, routers...)

	m := s.createMux() //真正的api注册
	if enableProfiler {
		profilerSetup(m)
	}
	s.routerSwapper = &routerSwapper{
		router: m,
	}
}

func (s *Server) createMux()完成真正的api注册

/*
	这里进行真正的路由注册,handler(f)
*/
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())
			/*
				在mux.Route路由结构中根据这个r.Path()路径设置一个适配器来匹配方法method和handler。
				当满足versionMatcher + r.Path()路径的正则表达式要求,就可以适配到相应的方法名及该handler
			*/
			m.Path(versionMatcher + r.Path()).Methods(r.Method()).Handler(f)
			m.Path(r.Path()).Methods(r.Method()).Handler(f)
		}
	}
	/*
		mux.Route没有找到请求数据所对应的方法或函数handler时的处理办法
	*/
	err := errors.NewRequestNotFoundError(fmt.Errorf("page not found"))
	notFoundHandler := httputils.MakeErrorHandler(err)
	m.HandleFunc(versionMatcher+"/{path:.*}", notFoundHandler)
	m.NotFoundHandler = notFoundHandler

	return m
}

5、image相关Api

// 初始化一个新的镜像的路由表
func NewRouter(backend Backend, decoder httputils.ContainerDecoder) router.Router {
	r := &imageRouter{
		backend: backend,
		decoder: decoder,
	}
	r.initRoutes()
	return r
}
// 初始化初始化映像路由器中的路由
func (r *imageRouter) initRoutes() {
	r.routes = []router.Route{
		// GET
		router.NewGetRoute("/images/json", r.getImagesJSON), /*建立一个Get方式的本地路由对象localRoute*/
		router.NewGetRoute("/images/search", r.getImagesSearch),
		router.NewGetRoute("/images/get", r.getImagesGet),
		router.NewGetRoute("/images/{name:.*}/get", r.getImagesGet),
		router.NewGetRoute("/images/{name:.*}/history", r.getImagesHistory),
		router.NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
		// POST
		router.NewPostRoute("/commit", r.postCommit),
		router.NewPostRoute("/images/load", r.postImagesLoad),
		router.Cancellable(router.NewPostRoute("/images/create", r.postImagesCreate)),
		router.Cancellable(router.NewPostRoute("/images/{name:.*}/push", r.postImagesPush)),
		router.NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
		router.NewPostRoute("/images/prune", r.postImagesPrune),
		// DELETE
		router.NewDeleteRoute("/images/{name:.*}", r.deleteImages),
	}
}

其中定义在/api/server/router/image/backend.go的type Backend interface声明了提供image相关功能需要实现的函数集合

/*
	type Backend interface声明了提供image相关功能需要实现的函数集合
*/
type Backend interface {
	containerBackend
	imageBackend
	importExportBackend
	registryBackend
}

type containerBackend interface {
	Commit(name string, config *backend.ContainerCommitConfig) (imageID string, err error)
}

type imageBackend interface {
	ImageDelete(imageRef string, force, prune bool) ([]types.ImageDelete, error)
	ImageHistory(imageName string) ([]*types.ImageHistory, error)
	Images(imageFilters filters.Args, all bool, withExtraAttrs bool) ([]*types.ImageSummary, error)
	LookupImage(name string) (*types.ImageInspect, error)
	TagImage(imageName, repository, tag string) error
	ImagesPrune(pruneFilters filters.Args) (*types.ImagesPruneReport, error)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

绝域时空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值